コピーコンストラクターとコピー代入演算子は、リソース(例えば動的メモリ)を所有するオブジェクトを扱うために必要です。デフォルトでは、コンパイラーはフィールドごとのコピーを生成しますが、これは生ポインタに対して安全ではありません。
class Buffer { public: Buffer(size_t size) { data = new int[size]; this->size = size; } ~Buffer() { delete[] data; } // 正しいコピーコンストラクターの実装 Buffer(const Buffer& other) : size(other.size) { data = new int[size]; std::copy(other.data, other.data + size, data); } // 正しい代入演算子 Buffer& operator=(const Buffer& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; std::copy(other.data, other.data + size, data); } return *this; } private: int* data; size_t size; };
さもなければ、二つのオブジェクトをコピーする際に、一方がメモリを解放し、もう一方はダングリングポインタに残ることになります(ダブルフリーまたは使用後の解放)。
コピーコンストラクターだけを明示的に宣言し、代入演算子を宣言しないとどうなりますか?それはいつ必要で、いつは必要ないのですか?
答え: もしコピーコンストラクターのみが宣言され、代入演算子が宣言されていない場合、コンパイラーはデフォルトの代入演算子(ビット単位のコピー)を生成しますが、これは動的リソースを含むオブジェクトに対して危険であり、同じポインタを二度破棄することになります。
メモリを管理する場合は、両方を実装する必要があります:コピーコンストラクターと代入演算子を実装し、コピーエラーやメモリリークを回避する必要があります。
逸話
メディアサーバーでは、バッファオブジェクトをコピーする際にコピーコンストラクターを使用しましたが、代入演算子を忘れました。一方のバッファをもう一方に代入すると、オブジェクトの破棄時に二重解放が発生しました。このエラーはストレステスト中に明らかになりました。
逸話
輸送ロジスティクスプロジェクトでは、デフォルトの代入演算子が座標配列のポインタを持つ構造体をコピーしました。一つのオブジェクトを削除すると、もう一つが解放されたメモリにアクセスし、セグメンテーションエラーを引き起こしました。
逸話
グラフィックリソースを扱うプロジェクトの一つでは、スレッド間でオブジェクトを渡す際にコピーコンストラクターを実装するのを忘れました。値を渡すことでシャローコピーが発生し、別のスレッドでオブジェクトが変更されたときにデータの破損とプログラムのクラッシュが発生しました。