コピーコンストラクタの使い方について

コピーコンストラクタが必要な場合について考えてみます。

インスタンスのコピー

クラスのインスタンスはメンバ変数を持っており、それぞれの値がメモリに保存されています。インスタンスをコピーすると、メンバ変数の値も丸ごとコピーされることになります。例えば、下のArrayクラスはm_varというメンバ変数を持っていますけれども、インスタンスをコピーするとm_varの値ももちろんそのままコピーされています。

class Array
{
public:
	Array(int var):m_var(var){}
	~Array(void){}
private:
	int m_var;
};
Array src=Array(5);
Array dest=src;	//dest.m_varは5に書き換えられる

次のようにポインタをメンバ変数として持っている場合も同様です。

class Array
{
public:
	Array(int* var):m_var(var){}
	~Array(void){}
private:
	int* m_var;
};
Array src=Array(0x0001);
Array dest=src;	//dest.m_varは0x0001の値に書き換えられる

そこで次のように、メンバ変数としてポインタを持っていて、その領域にメモリを動的に確保する場合を考えてみます。

class Array
{
public:
	Array();
	~Array(void);
private:
	int* m_var;
};

#include <iostream>
using namespace std;
Array::Array()
{
	m_var=new int[3];
	cout<<"アドレス:"<<m_var<<"にメモリ領域を確保"<<endl;
}
Array::~Array(){
	cout<<"アドレス:"<<m_var<<"のメモリ領域を解放"<<endl;
	delete[] m_var;
}

int main()
{
	Array src;	//constructor		new src.m_var=00995308(メモリ確保)
	Array dest=src;	//copy constructor	copy dest.m_var=src.m_var=00995308(メモリ確保しない)
	return 0;
}
//destructor dest.m_var ... delete 00995308(メモリ領域を確保)
//destructor src.m_var ...  delete 00995308(メモリ領域を確保)

処理の順を追ってみると、
まずsrc生成時に普通のコンストラクタが呼び出され、00995308にメモリ領域を確保します。
次にdest生成時には、通常のコンストラクタではなく、何も処理されないコピーコンストラクが呼び出されますので、新たに領域が確保されません。
destのデストラクタでアドレス00995308をdeleteします。
しかしさらにsrcのデストラクタで同じ領域deleteするので、値がコピーされずにdeleteを2重に呼び出す結果になります。


アドレス:00995308にメモリ領域を確保
アドレス:00995308のメモリ領域を解放
アドレス:00995308のメモリ領域を解放

コピーコンストラクタによる代理処理

通常のコンストラクタの代わりにコピーコンストラクタを追加して代理処理の実装を行います。追加するべき処理は、

  1. 新しい領域を確保する
  2. コピー元の値を複製する

の2つです。

class Array
{
public:
	Array(void);
	~Array(void);
	Array(const Array& ary);	//copy constructor
private:
	int* m_var;
};

#include <iostream>
using namespace std;
Array::Array()
{
	m_var=new int[3];
	cout<<"アドレス:"<<m_var<<"にメモリ領域を確保"<<endl;
}

Array::Array(const Array& ary){
	m_var=new int[3];
	cout<<"アドレス:"<<m_var<<"にメモリ領域を確保"<<endl;
	for(int i=0;i<3;i++){
		m_var[i]=ary.m_var[i];
	}
	cout<<"領域の内容をコピー"<<endl;
}

Array::~Array(){
	cout<<"アドレス:"<<m_var<<"のメモリ領域を解放"<<endl;
	delete[] m_var;
}

int main()
{
	Array ary;	
	Array ary2=ary;

	return 0;
}


アドレス:007C8FC0にメモリ領域を確保
アドレス:007C2328にメモリ領域を確保
領域の内容をコピー
アドレス:007C2328のメモリ領域を解放
アドレス:007C8FC0のメモリ領域を解放

コピーコンストラクタを定義しなかったために生じた問題を考えてみると、

  • コピーコンストラクタが呼ばれない→newと値の複製がなされない
  • デストラクタが呼ばれる→2回呼び出される

この状況で何かまずい問題が発生するような処理は基本的にコピーコンストラクタを使わなければなりません。
new⇔deleteの関係に類似した処理であるかどうかがコピーコンストラクタを使うかどうかの判断基準になりそうです。。