深い/浅い コピーについて

インスタンスのコピーには、深いのと浅いのがある。
C++のクラスにおいて自動生成されるコピー動作(コピーコンストラクタ)は、「浅い」コピー。
浅いコピーは、基本的にメンバを全て複製する。
まぁ・・・コピーなんだから当然なのだけど、コピーされるオブジェクトの性質によっては、マズい事が起こる。
例えば、次のソースを見てみる。

#include <iostream>
using namespace std;

class Object{
private:
  int *array;
public:
  // 配列のnum番の要素を返す
  int getArray(int num){
    return array[num];
  }
  // コンストラクタ。任意の大きさのint配列を生成し、0で初期化。
  Object(int num){
    array = new int[num];
    for(int i=0; i<num; i++){
      array[i] = 0;
    }
  }
  // デストラクタ。動的に確保したarrayを削除する。
  // もちろん、しないとメモリリーク。
  ~Object(){
    delete [] array;
  }
};


int main(){
  Object obj(10);
  cout << obj.getArray(5) << endl;
  {
    Object obj2 = obj;
  }
  cout << obj.getArray(5) << endl;
  return 0;
}

一見、なんの問題もなさそうなコードに見える。
が。
私が動かした環境での実行結果は以下。

0
-1785358955

見事に中身がぶっ壊れてますね。ええ。
何故こうなるか、っていうのを軽く解説してみましょう。


結論から言えば、
『コピーしたインスタンスが、スコープ外れて削除されたので、
 正常にデストラクタが働いてオブジェクト間で共有していたintの配列がdeleteされた』
からです。


デフォルトのコピーコンストラクタは『メンバのコピー』です。
さて、Objectが持っている配列ですが……正確には、Objectが配列を持っているわけではありません。
Objectが持っているのは、int型の"配列を指している"ポインタなワケです。
指しているポインタを複製しても、実際にメモリ上に確保されたインスタンスが変化するわけではありません。
しかし、デストラクタでは確保したメモリ領域を開放しています。
だから、コピー(ポインタが複製)→コピーが削除(インスタンスが削除)→削除されたインスタンスを参照しようとしてバグる
と、まあこうなるわけです。


深いコピーを行う場合には自前でコピーコンストラクタを書いて、その中で新しくint配列をnewする必要があります。
こういった事を頭の片隅にでも入れておかないと、後々とれないバグが出ることもあるから気をつけようね的な記事でした。