ProgrammingC++ 開発者

動的メモリを使用するクラスにおける浅いコピーと深いコピーの違いを説明してください。なぜ深いコピーコンストラクターが必要ですか?手動で深いコピーを実装するにはどうすればよいですか?

Hintsage AIアシスタントで面接を突破

回答。

質問の背景

C++では、オブジェクトをコピーする際にメンバー毎のコピー機構が使用されます。オブジェクトの各メンバーに対して、独自のコピー操作が呼び出されます。組み込み型にとっては安全ですが、動的リソースに対しては問題が発生します。ポインターのみがコピーされ、データ自体はコピーされないからです。

問題

もしオブジェクトがヒープメモリに割り当てられたポインターを含む場合、2つのオブジェクトをコピーした後、それらは同じメモリ領域を指すことになります。そのため、一方のオブジェクトが破棄されるとメモリが解放され、もう一方のポインターは無効(「野生」ポインター)になります。これにより、実行時エラーやメモリリークが発生する可能性があります。

解決策

コピーが独立するためには、深いコピー—バイト単位のコピーと専用バッファの割り当てが必要です。これはカスタムコピーコンストラクターと代入演算子を作成することで実現されます。

コード例:

class MyString { char* data; public: MyString(const char* s) { data = new char[strlen(s)+1]; strcpy(data, s); } // 深いコピーコンストラクター MyString(const MyString& src) { data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } // 深いコピー代入演算子 MyString& operator=(const MyString& src) { if (this != &src) { delete[] data; data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } return *this; } ~MyString() { delete[] data; } };

主な特徴:

  • 外部リソースを管理するクラスには必須
  • 独自のコピーコンストラクターおよびoperator=の実装が必要
  • 「Rule of Three」を正しく実装しないとメモリリークが発生する可能性があります。

隠れた質問。

クラスにポインターのみが含まれ、メモリが割り当てられていない場合、なぜカスタムデストラクターが必要ですか?

デストラクターは、クラス内で明示的にメモリ(または他のリソース)を割り当てた場合にのみ必要です。ポインターがメモリを割り当てない場合、デフォルトのデストラクターで十分です。


動的メモリを持つクラスでoperator=を実装しない場合、コピーコンストラクターが宣言されていても何が起こりますか?

コピーコンストラクターを手動で定義した場合、コンパイラは自動的にoperator=を実装しません。暗黙的に宣言されるか、コンパイラがエラー/警告を出すことになります(標準によります)。これは、代入の際に悪化した動作を引き起こします:メンバー毎のコピーが行われ、ダブルフリーまたはメモリリークが発生します。

コード例:

MyString a("hi"); MyString b = a; // OK: あなたのコピーコンストラクター MyString c("bye"); c = a; // 問題! operator=が手動で実装されていない場合、浅いコピーになります。

手動でoperator=を実装する際に自己代入を行うことの危険性は何ですか?

リソースを共有する場合、this!=&rhsをチェックせずに自己代入を行うと、delete[] dataが実行された後、すでに解放された配列をコピーしようとしてセグメンテーションフォルトを引き起こします。自己保護: 常に自己代入を確認してください。

if (this != &rhs) { ... }

一般的なミスとアンチパターン

  • Rule of Three のメソッドの一つ(コピーコンストラクタ、operator=、デストラクタ)が実装されていない
  • 自己代入がチェックされていない
  • 忘れられたdelete[]によるメモリリーク
  • ポインターが共有された場合のダブルフリー

実生活の例

ネガティブケース

開発者は、深いコピーを実装せずに組み込みポインタを持つクラスのオブジェクトをコピーします。コピー後、複数のオブジェクトが同じメモリ領域を共有します。2つのデストラクタが呼び出され、ダブル削除が発生し、プログラムがクラッシュします。

利点:

  • コードを書くのが簡単なこと(「コピーが動作する」ように見える)

欠点:

  • プログラムのクラッシュ、不確実なバグ
  • メモリリークまたはダブルフリー

ポジティブケース

開発者は、コピーコンストラクタ、代入演算子、デストラクタを適切に実装します。各オブジェクトは自分のメモリを所有します。

利点:

  • コピーと削除時の安全性
  • メモリリークがない

欠点:

  • 多くのコピーがある場合、コピーのオーバーヘッドが増加します。