5

で同じコードを繰り返さないようにしてください。クラスに動的に割り当てられたデータが含まれている場合は、コピーコンストラクタoperator =とdestructorを明示的に定義するのが一般的です。しかし、これらの特別な方法の活動は重なっている。より具体的にはoperator =通常は最初にいくつかの破壊を行い、コピーコンストラクタの場合と同様に対処します。コピーコンストラクタとoperator =

私の質問は、同じ行のコードを繰り返さずに、プロセッサが不要な作業(不要なコピーなど)をする必要なしに、これを最良の方法で書く方法です。

私は通常、2つの手助けの方法で終了します。 1つは建設用、もう1つは破壊用です。最初はコピーコンストラクタとoperator =の両方から呼び出されます。 2番目は、destructorとoperator =によって使用されます。ここで

はサンプルコードです:

template <class T> 
    class MyClass 
    { 
     private: 
     // Data members 
     int count; 
     T* data; // Some of them are dynamicly allocated 
     void construct(const MyClass& myClass) 
     { 
      // Code which does deep copy 
      this->count = myClass.count; 
      data = new T[count]; 
      try 
      { 
       for (int i = 0; i < count; i++) 
        data[i] = myClass.data[i]; 
      } 
      catch (...) 
      { 
       delete[] data; 
       throw; 
      } 
     } 
     void destruct() 
     { 
      // Dealocate all dynamicly allocated data members 
      delete[] data; 
     } 
     public: MyClass(int count) : count(count) 
     { 
      data = new T[count]; 
     } 
     MyClass(const MyClass& myClass) 
     { 
      construct(myClass); 
     } 
     MyClass& operator = (const MyClass& myClass) 
     { 
      if (this != &myClass) 
      { 
       destruct(); 
       construct(myClass); 
      } 
      return *this; 
     } 
     ~MyClass() 
     { 
      destruct(); 
     } 
    }; 

も、正しいこれですか? そして、この方法でコードを分割するのは良い習慣ですか?

+0

+1という質問が私の意識を高めるのに役立ったからです。答えを読む前に、私が書いたようなものです。 – ToolmakerSteve

+0

私はまったく異なるものを扱うので、両者でコードが重複することはほとんどありません。一つは初期化され、一つは.... – PlasmaHH

+0

は重複につながる彼のクラスデザインの "ディープコピー"の性質です。 – ToolmakerSteve

答えて

0

まず、右側をコピーしてからスワップして割り当てを実装します。このようにして、上記のコードでは提供しない例外安全性も得られます。メンバーポインタがいくつかの割り当て解除されたデータを参照するので、destroy()が成功した後にconstruct()が失敗したときに壊れたコンテナで終わる可能性があります。

foo& 
foo::operator=(foo const& rhs) 
{ 
    using std::swap; 
    foo tmp(rhs); 
    swap(*this, tmp); 
    return *this; 
} 
+0

構造が失敗した場合、古い)の内容ではなく、空のコンテナを使用しますか? IMHO空のコンテナに失敗したコピー結果があるときれいになります。具体的には、空のコンテナは後のコードで検出するのが簡単です。コンテンツを持っているべきではない容器は、後に検出するのがより困難です。 – ToolmakerSteve

+0

このようなエラーが発生した場合にプログラムを終了したいとしたら、どちらの方法でもうまく動作しますが、元のコードでは失敗した 'data = nullptr'を設定する必要があります。 – riv

+0

@ToolmakerSteveそれはあなたが望むものによって決まります。スワップイディオムは完全なトランザクションの完全性を保証します。あなたは成功するか、何も変わりません。ほとんどの場合、完全なトランザクションの整合性は個々のオブジェクトには必要ありません。あなたが保証しなければならないのは、オブジェクトが失敗した場合に破棄できることだけです。 (実際の状態が変わらないことを保証しようとするのはあまり有用でないかもしれません) –

0

仮想の構築や破壊を宣言しない限り、それに固有の問題はありません。

Effective C++(Scott Meyers)の第2章に興味があるかもしれませんが、これはコンストラクタ、コピー演算子、デストラクタに完全に専念しています。

あなたのコードで扱われない例外については、11の項目をもっと効果的なC++(Scott Meyers)で検討してください。

+1

それ以外は例外ではありません。 'construct 'の' new'がスローする(またはコピーがスローされる)と、オブジェクトは非干渉状態になり、オブジェクトを破壊すると未定義の動作が発生します。 –

+0

@JamesKanzeもちろん、あなたは正しいですが、問題はコードの重複を避けるためのテクニックです。このテクニックには固有の問題はありません。 –

+0

固有の問題はありません。それは動作しない_except_です。例外安全性はオプションではありません。プログラムが正しければ不可欠です。 –

5

つの初期のコメント:operator=ない開始 消滅することにより、しかし構築することにより行います。それ以外の場合、 例外を介して終了すると、 オブジェクトは無効な状態になります。このため、コードが正しくありません。 (自己割り当てのためにテストする 必要が通常 代入演算子は正しくないであるというサインであることに注意してください)

これを処理するための古典的な溶液は、スワップイディオムです:あなたは、メンバ関数のスワップを追加 :

void MyClass:swap(MyClass& other) 
{ 
    std::swap(count, other.count); 
    std::swap(data, other.data); 
} 

これは投げないことが保証されています。 (ここでは、それだけでint型 と投げることができますどちらもポインタを、交換します。)次に、あなたのように代入演算子を実装 :

MyClass& MyClass<T>::operator=(MyClass const& other) 
{ 
    MyClass tmp(other); 
    swap(tmp); 
    return *this; 
} 

これは、シンプルでまっすぐ進むですが、その中 すべてのいずれかの解決策操作が失敗する可能性があります開始する前に完了 データを変更することが許容されます。例えば、あなたの コードのような単純なケースについては、:

MyClass& MyClass<T>::operator=(MyClass const& other) 
{ 
    T* newData = cloneData(other.data, other.count); 
    delete data; 
    count = other.count; 
    data = newData; 
    return *this; 
} 

cloneDataが あなたconstructはありませんが、ポインタを返し、 はthisには何も変更しないものの大半を行うメンバー関数であります)。

EDIT:

直接あなたの最初の質問に関連しますが、一般的に、 このような場合には、あなたがはないnew T[count]cloneData(またはconstruct、または何を)やりたいしないでください。これにより、Tのすべての がデフォルトコンストラクタで構築され、割り当てられます。

ほとんどの場合、これは別の(プライベート)マネージャ クラス使用して行われます
T* 
MyClass<T>::cloneData(T const* other, int count) 
{ 
    // ATTENTION! the type is a lie, at least for the moment! 
    T* results = static_cast<T*>(operator new(count * sizeof(T))); 
    int i = 0; 
    try { 
     while (i != count) { 
      new (results + i) T(other[i]); 
      ++ i; 
     } 
    } catch (...) { 
     while (i != 0) { 
      -- i; 
      results[i].~T(); 
     } 
     throw; 
    } 
    return results; 
} 

// Inside MyClass, private: 
struct Data 
{ 
    T* data; 
    int count; 
    Data(int count) 
     : data(static_cast<T*>(operator new(count * sizeof(T))) 
     , count(0) 
    { 
    } 
    ~Data() 
    { 
     while (count != 0) { 
      -- count; 
      (data + count)->~T(); 
     } 
    } 
    void swap(Data& other) 
    { 
     std::swap(data, other.data); 
     std::swap(count, other.count); 
    } 
}; 
Data data; 

// Copy constructor 
MyClass(MyClass const& other) 
    : data(other.data.count) 
{ 
    while (data.count != other.data.count) { 
     new (data.data + data.count) T(other.date[data.count]); 
     ++ data.count; 
    } 
} 

(そしてもちろん、スワップイディオムをこれを行う 慣用的な方法のようなものです割り当てのために)。これにより、 複数のカウント/データのペアは、安全性を失うリスクなしで の安全性を確保できます。

+0

+1、ありがとう@ジェームス、私は今このトピックをよりよく理解しています。 – ToolmakerSteve

+0

これは複数の価値がある革命的なものです。+ - "自己割り当てをテストする必要性は、通常、代入演算子が正しくないことを示しています。" – SChepurin

+0

@ James Kanze:1つのケース(同僚が遭遇したケース)は、代入演算子がリソースの1つに対してmemcpyを実行する必要がある場合です。その場合、自己割り当てが必要になります。 – ForeverLearning

関連する問題