2016-06-15 2 views
2

私はビッグ5(コピーコンストラクタ、コピー代入演算子、移動コンストラクタ、移動代入演算子、デストラクタ)の作成に取り組んでいます。そして私は、コピーコンストラクタの構文を少しぶつけました。代入オーバーロード構文のコピーコンストラクタ?

は、私は、次のプライベートメンバを持つクラスのfooを持って言う:

template<class data> // edit 
    class foo{ 

    private: 
     int size, cursor; // Size is my array size, and cursor is the index I am currently pointing at 
     data * dataArray; // edit 
    } 

私はいくつかの任意のサイズXのこのためのコンストラクタを記述した場合、それは次のようになります。私は以下のコードから=オーバーロードしていると仮定すると、

template<class data> // edit 
foo<data>::foo(foo &bar){ 
foo = bar; // is this correct? 
} 

私は次のようにする必要があると思います barと呼ばれる別のオブジェクトのコピーコンストラクタを作成したい場合は今

template<class data> // edit 
foo<data>::foo(int X){ 
    size = X; 
    dataArray = new data[size]; 
    cursor = 0; // points to the first value 
} 

template<class data> // edit 
    foo<data>::operator=(foo &someObject){ 
     if(this != someObject){ 
      size = someObject.size; 
      cursor = someObject.cursor; 
      delete[] dataArray; 
      dataArray = new data[size]; 
      for(cursor = 0; cursor<size-1;cursor++) 
       dataArray[cursor] = someObject.dataArray[cursor]; 
      } 

     else 
      // does nothing because it is assigned to itself 
     return *this; 
     } 

コピーコンストラクタは正しいですか?または、*this = barの代わりにfoo = barを指定する必要がありますか?

テンプレートのコンストラクタはまだ新しくなっていますので、コードでエラーが発生した場合は修正してください。

EDIT 1:マルチンによって下方に設けられた答えのおかげで、私は彼らが下のリストにまとめて、それはより多くの構文上正しい作るために上記のコードにいくつかの編集を行い、//editでそれらをコメントしている:

  1. 以前はtemplate<classname data>ですが、間違っているのはそれぞれ関数とクラスがtemplate <typename data>またはtemplate <class data>である必要があります。
  2. 以前int*dataArray;これは、テンプレートをmissusesとあなたのFooクラスは内部dataテンプレートパラメータを使用していませんdata* dataArray;
+4

** 1 **コピーコンストラクタを記述して、各メンバーをr.h.sから初期化します。オブジェクト(あなたの現在の 'op ='と同じように)。 ** 2。**コピーコンストラクタに関してコピーの割り当てを記述します。 [コピーとスワップイディオム](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap)(こちら[こちら](http://stackoverflow.com/questions/3279543)/what-is-the-copy-and-swap-idiom))を参照してください。このようにしなければならない理由は、現在の実装が行うコピーコンストラクタを記述する際に、デフォルトの初期化を行わずに代入するのを避けることです。 – Andrew

+1

参照:http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom – NathanOliver

+3

これは '* this = bar'で、コピーコンストラクタは' const'によってソースを取得する必要があります参照。また、代入演算子は実装が簡単で、[コピーとスワップイディオム](http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom?rq=1)を使用するとエラーが発生しにくい)。しかし、この場合、単に 'dataArray'と' size'を 'std :: vector'に置き換え、コピー/ムーブコンストラクタとデストラクタのデフォルトをすべて残しておかなければなりません。代入演算子も簡単になります。 –

答えて

1

でなければなりません。私はあなたがここでそれを使用したいと仮定します。あなたはまた、classnameキーワードが、typenameまたはclassを使用することはできません

int * dataArray; // should be: data * dataArray; 

。あなたのコードには他にもたくさんのコンパイルエラーがあります。

あなたのコピーコンストラクタが間違っている、それがコンパイルされません。

foo = bar; // is this correct? - answer is NO 

fooは、この文脈では、クラス名であるので、あなたの仮定が正しいです。 *this = someObjectこれは機能します(追加の修正では、少なくともdataArrayはnullptrに設定する必要があります)。しかし、クラス変数は、代入演算子によって上書きされるように、最初はコピーコンストラクタによってデフォルトで構築されるため、もっとここで読むの場合:

Calling assignment operator in copy constructor

Is it bad form to call the default assignment operator from the copy constructor?

+0

'nullptr'に設定していますか?これは、配列がコンストラクタで初期化される必要があることを意味しますか?あなたの答えは 'operator =(){}'が設定されていることを意味しますか?私はこれが少し面倒かもしれないが、他のコンパイルエラーを私に示すことができます知っていますか?私はあなたが指摘したいくつかの事柄に基づいてコードを修正しました。それを見てください。 – Callat

+1

@Hikari * this = barの前にdataArrayをnullptrに設定しない場合は、 operator =は、指定されていない値を持つdataArrayに対してdelete []を呼び出します。コンパイルエラーのある他の場所を表示するには、コードを次のコードと比較してください。http://coliru.stacked-crooked.com/a/6f78eedbee39cf6a – marcinj

2

あなたが望むものを達成するための最良の方法はあなたのためにそのメモリ管理の世話をし、コピーや移動、すでに割り当てを処理するクラスを使用することです。 std::vectorはこれを正確に行い、動的に割り当てられた配列とサイズを直接置き換えることができます。これを行うクラスは、しばしばRAIIクラスと呼ばれます。こと、これは正しく、様々な特殊なメンバ関数を実装する際の運動であると仮定すると、私はあなたがcopy and swap idiomを介して進行することをお勧めしたいと述べた


。 (詳しくは、What is the copy and swap idiom?を参照)。アイデアは、コピーコンストラクタの観点から割り当て操作を定義することです。

メンバー、コンストラクタ、デストラクタから始めます。これらはあなたのクラスのメンバーの所有権の意味を定義する:

ここ
template <class data> 
class foo { 
public: 
    foo(const size_t n); 
    ~foo(); 

private: 
    size_t size; // array size 
    size_t cursor; // current index 
    data* dataArray; // dynamically allocated array 
}; 

template <class data> 
foo<data>::foo(const size_t n) 
: size(n), cursor(0), dataArray(new data[n]) 
{} 

template <class data> 
foo<data>::~foo() { 
    delete[] dataArray; 
} 

、メモリはコンストラクタに割り当てられ、デストラクタで割り当て解除されます。 次に、コピーコンストラクタを記述します。

template <class data> 
foo<data>::foo(const foo<data>& other) 
: size(other.size), cursor(other.cursor), dataArray(new data[other.size]) { 
    std::copy(other.dataArray, other.dataArray + size, dataArray); 
} 

(クラス本体内には、foo(const foo& other);が含まれています)。 メンバ初期化子リストを使用して、メンバ変数をotherオブジェクトの値に設定する方法に注目してください。新しい割り当てが実行され、コピーコンストラクタの本体で、otherオブジェクトのデータをこのオブジェクトにコピーします。

次は代入演算子です。あなたの既存の実装は、ポインタの多くの操作を実行する必要があり、例外的な安全ではありません。のは、これは、より簡単に行われ、より安全にすることができる方法を見てみましょう:

template <class data> 
foo<data>& foo<data>::operator=(const foo<data>& rhs) { 
    foo tmp(rhs); // Invoke copy constructor to create temporary foo 

    // Swap our contents with the contents of the temporary foo: 
    using std::swap; 
    swap(size, tmp.size); 
    swap(cursor, tmp.cursor); 
    swap(dataArray, tmp.dataArray); 

    return *this; 
} 

(イン・クラスの宣言、foo& operator=(const foo& rhs);と一緒に)。

[ - 脇に:関数の引数を値として受け入れることで、最初の行(明示的にオブジェクトをコピーする)を書くことを避けることができます。それは同じことだし、いくつかのケースでは、より効率的かもしれません:

template <class data> 
foo<data>& foo<data>::operator=(foo<data> rhs) // Note pass by value! 
{ 
    // Swap our contents with the contents of the temporary foo: 
    using std::swap; 
    swap(size, rhs.size); 
    swap(cursor, rhs.cursor); 
    swap(dataArray, rhs.dataArray); 

    return *this; 
} 

あなたも移動代入演算子を定義する場合は、そうすることがあいまいなオーバーロードを引き起こす可能性があります。 - ]

最初に行うことは、割り当て元のオブジェクトのコピーを作成することです。これは、コピーコンストラクタを使用するので、オブジェクトのコピー方法の詳細は、コピーコンストラクタで一度だけ実装する必要があります。

コピーが完了したら、内部のコピーをコピーの内部と交換します。関数本体の最後に、tmpコピーが範囲外になり、デストラクタがメモリをクリーンアップします。しかし、これは関数の初めに割り当てられたメモリではありません。それは私たちのオブジェクトを使用して、私たちの状態を一時的に交換する前に、保持するためにを使用しました。

このようにして、割り当て、コピー、および割り当て解除の詳細は、コンストラクタとデストラクタでそれらが属する場所に保持されます。代入演算子スワップをコピーします。

これ以上の利点がありますが、それ以上に簡単です。例外的に安全です。上記のコードでは、割り当てエラーにより、一時的な作成中に例外がスローされる可能性があります。しかし、私たちはまだクラスの状態を変更していないので、割り当てが失敗しても状態は一貫しています。


同じロジックに従って、移動操作は簡単になります。移動コンストラクタは、単にリソースの所有権を取得し、ソース(移動元オブジェクト)を明確な状態にするために定義する必要があります。つまり、ソースのdataArrayメンバをnullptrに設定し、デストラクタ内の次のdelete[]が問題を起こさないようにします。

移動代入演算子はコピー代入と同様に実装することができますが、この場合、ソースオブジェクトの既に割り当てられたメモリを盗んでいるだけなので、例外安全性への懸念は少なくなります。完全なサンプルコードでは、単に状態をスワップすることを選択しました。

コンパイルおよび実行可能な完全な例は、hereで見ることができます。

+0

素晴らしい答えをありがとう。私はまだRAIIをまだ手に入れていませんが、小規模のテストケースでは練習をしています。私は本当にあなたの答えから多くを学んだが、シンプルさの点で、私が持っていた問題のために実装するのが簡単であることが分かった。だから私はそれを受け入れた。 – Callat

関連する問題