C++で値渡しでオブジェクトを関数に渡すと、コピーコンストラクタを使用してオブジェクトのコピーが作成されます。クラスにユーザー定義のコピーコンストラクタが定義されている場合、それが関数の引数としての一時オブジェクトを初期化するために呼び出されます。定義されていない場合、コンパイラによってデフォルトが使用され、ビット単位のコピー(shallow copy)が実行されます。
注意点:
例:
class StringWrapper { char* data; public: StringWrapper(const char* str) { data = new char[strlen(str) + 1]; strcpy(data, str); } // エラー: shallow copy StringWrapper(const StringWrapper& other) : data(other.data) {} ~StringWrapper() { delete [] data; } }; void foo(StringWrapper s) { // ... } int main() { StringWrapper s1("hello"); foo(s1); // UB!!! return 0; }
"ポインタを持つクラスでコピーコンストラクタを次のように定義した場合、何が起こりますか:
MyClass(const MyClass &other) : data(other.data) {}? これによってどのような結果になりますか?"
正しい回答: このようなコピーコンストラクタは、コピー元と同じメモリ領域を指すオブジェクトを作成します。二つのオブジェクトが破棄されると、メモリが二重に解放され(double free)、未定義の動作を引き起こします。deep copyを実装する必要があります:
MyClass(const MyClass &other) { data = new int(*other.data); }
物語
大規模なサーバープロジェクトで、内部に"生"の配列を持つオブジェクトのコンテナが、標準のコピーコンストラクタ(shallow copy)を使用して利用されることがありました。オブジェクトを値渡しで渡すと、ダブルフリーが発生し、アプリケーションがクラッシュし、プロダクション環境でのみキャッチされていました。
物語
古いC++画像処理ライブラリでは、コピーコンストラクタがグラフィックバッファをコピーせず、片方のコピーの変更がもう片方に影響を与え、インターフェースで予期しないバグを引き起こしました。
物語
パスワードの内部ストレージシステムの一つからの関数からの値渡しの結果、オブジェクトが破棄されるときに一時オブジェクトがクリーンアップされ(shallow copy)、実際のオブジェクトがゼロポインタを保持し、リークはセキュリティ監査の過程で偶然発見されました。