コピーコンストラクタ

2016年06月07日 (火) 15時38分43秒
このエントリーをはてなブックマークに追加

コピーコンストラクタ

コピーコンストラクタは次のように宣言されるコンストラクタである

class MyClass{
public:
	MyClass(const MyClass &obj);
};

これは次のようなときに呼び出される
1.初期化するとき

MyClass obj2 = obj1;// ここでコピーコンストラクタが呼ばれる 引数はobj1
obj2 = obj1;// これは代入なのでコピーコンストラクタは呼ばれない
MyClass obj3(obj1);// こういう書き方でもコピーコンストラクタが呼ばれる

2.引数で値渡しするとき

3.戻り値で値渡しをするとき

MyClass func(MyClass obj){
	return obj;
}

コピーコンストラクタはデフォルトのコピーコンストラクタがあって、それはメンバ変数をすべてコピーする
なのでわざわざコピーコンストラクタを宣言しなくてもコピーしてくれるのである
もしコピーされたくないクラスがあれば次のようにすればよい

class MyClass{
private:
	MyClass(const MyClass &obj);
	MyClass& operator=(const MyClass &obj);
public:
};

コピーコンストラクタと代入演算子=がprivateとなって外部から呼び出せなくなるのである
ちなみにコンストラクタもprivateにすると生成すらできなくなる

さて、デフォルトのコピーコンストラクタはメンバ変数をすべてコピーするのであった
では、次のプログラムはどうなるだろう?

class Circle{
public:
	double x,y;
	double radius;

	Circle():x(0.0),y(0.0),radius(0.0){}
	Circle(double tx,double ty,double tr):x(tx),y(ty),radius(tr){}
};

class Character{
public:
	Circle *pointer;

	Character(){pointer = new Circle();}
	~Character(){delete pointer;}
};

double distance(Character obj){
	return obj.pointer->x + obj.pointer->y;
}

int main(){
	Character player;
	double d = distance(player);
	return 0;
}

キャラクタークラスはコンストラクタでメモリを確保し、デストラクタでメモリを解放している
このプログラムは実行時にエラーが発生する

何がいけないのか
原因はポインタの値がそのままコピーされたことである
main関数で宣言されたplayerはまずコンストラクタでメモリを確保する
その後、distance関数の引数に値渡しされるときに、そのメモリの値がobjに”コピー”される
そしてdistance関数が終わってobjが破棄されるときにデストラクタが呼ばれて、メモリを解放してしまうのである
かくしてplayerのポインタは解放されたメモリを参照し、自身が破棄されるときにそのメモリをさらに解放しようとしてエラーが出るのである

このような事態を回避するためのひとつの方法として、コピーコンストラクタを変更するということがある
次のようにするのである

class Character{
public:
	Circle *pointer;

	Character(){pointer = new Circle();}
	Character(const Character &obj){pointer = new Circle(*obj.pointer);}
	~Character(){delete pointer;}
};

これでコピーコンストラクタが呼ばれてもメモリを新しく確保するのでデストラクタでコピー元を破壊しなくてすむ

次のように値渡しではなく参照渡しするのも良い

double distance(const Character &obj){
	return obj.pointer->x + obj.pointer->y;
}

戻り値に使いたいときなどはコピーコンストラクタ(と代入演算子)を変えるのが良いだろう

(2013/5/29)代入演算子内で古いpointerを破棄し忘れていたのを訂正