コピーコンストラクタ のバックアップの現在との差分(No.1)


  • 追加された行はこの色です。
  • 削除された行はこの色です。
コピーコンストラクタ

コピーコンストラクタは次のように宣言されるコンストラクタである
#code(cpp){{
class MyClass{
public:
	MyClass(const MyClass &obj);
};
}}
これは次のようなときに呼び出される
1.初期化するとき
#code(cpp){{
MyClass obj1;
MyClass obj2 = obj1;// ここでコピーコンストラクタが呼ばれる 引数はobj1
obj2 = obj1;// これは代入なのでコピーコンストラクタは呼ばれない
MyClass obj3(obj1);// こういう書き方でもコピーコンストラクタが呼ばれる
}}
2.引数で値渡しするとき

3.戻り値で値渡しをするとき
#code(cpp){{
MyClass func(MyClass obj){// 引数を受け取ったときにコピーコンストラクタが呼ばれる
	return obj;// 値を返すときにコピーコンストラクタが呼ばれる
MyClass func(MyClass obj){
	return obj;
}
}}
コピーコンストラクタはデフォルトのコピーコンストラクタがあって、それはメンバ変数をすべてコピーする

なのでわざわざコピーコンストラクタを宣言しなくてもコピーしてくれるのである

もしコピーされたくないクラスがあれば次のようにすればよい
#code(cpp){{
class MyClass{
private:
	MyClass(const MyClass &obj);
	MyClass& operator=(const MyClass &obj);
public:
};
}}
コピーコンストラクタと代入演算子=がprivateとなって外部から呼び出せなくなるのである
ちなみにコンストラクタもprivateにすると生成すらできなくなる

さて、デフォルトのコピーコンストラクタはメンバ変数をすべてコピーするのであった
では、次のプログラムはどうなるだろう?
#code(cpp){{
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;}
};

// 原点から円までの距離の2乗を求める
double distance(Character obj){
	double x = obj.pointer->x;
	double y = obj.pointer->y;
	double r = obj.pointer->radius;
	return x*x+y*y-r*r;
	return obj.pointer->x + obj.pointer->y;
}

int main(){
	Character player;
	double d = distance(player);
	return 0;
}
}}
キャラクタークラスはコンストラクタでメモリを確保し、デストラクタでメモリを解放している
このプログラムは実行時にエラーが発生する

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

このような事態を回避するためのひとつの方法として、コピーコンストラクタを変更するということがある
次のようにするのである
#code(cpp){{
class Character{
public:
	Circle *pointer;

	Character(){pointer = new Circle();}
	Character(const Character &obj){pointer = new Circle(*obj.pointer);}
	~Character(){delete pointer;}
};
}}
これでコピーコンストラクタが呼ばれてもメモリを新しく確保するのでデストラクタでコピー元を破壊しなくてすむ
尤も値渡しではなく参照渡しなどにすれば済む話ではあるのだが

次のように値渡しではなく参照渡しするのも良い
#code(cpp){{
// 原点から円までの距離の2乗を求める
double distance(const Character &obj){
	double x = obj.pointer->x;
	double y = obj.pointer->y;
	double r = obj.pointer->radius;
	return x*x+y*y-r*r;
	return obj.pointer->x + obj.pointer->y;
}
}}
戻り値に使いたいときなんかはコピーコンストラクタ(と代入演算子)を変えるのが良いのかもしれない
#code(cpp){{
class Character{
public:
	Circle *pointer;

	Character(){pointer = new Circle();}
	Character(const Character &obj){pointer = new Circle(*obj.pointer);}
	~Character(){delete pointer;}
	Character& operator=(Character&obj){
		pointer = new Circle(*obj.pointer);
		return obj;
	}
};
// objをdx,dy移動させた新しいインスタンスを返す
Character func(const Character &obj,int dx,int dy){
	Character new_obj;
	new_obj.pointer->x = obj.pointer->x + dx;
	new_obj.pointer->y = obj.pointer->y + dy;
	new_obj.pointer->radius = obj.pointer->radius;
	return new_obj;
}
}}
戻り値に使いたいときなどはコピーコンストラクタ(と代入演算子)を変えるのが良いだろう

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