コピーコンストラクタ


コピーの初期化定義

以前、オブジェクトの関数間の値渡しの問題について少し触れました
関数にオブジェクトを渡したり、あるいは戻り値として受け取った場合
オブジェクトはビット単位でそっくりそのままコピーされます

そのオブジェクトが動的に割り当てたメモリ領域へのポインタ型メンバ変数を保有していた場合
このポインタのアドレスも同じものを指すので、データの整合性が保てなくなります
また、それぞれのオブジェクトのデストラクタ関数で2度にわたって同じ領域が解放される危険性もあります

この問題を回避するにはコピー用の初期化を作成することです
オブジェクトのコピーがとられた時に実行する処理をコピーコンストラクタといいます

コピーコンストラクタは、オブジェクトの初期化時に呼び出されるもので
宣言時、引数、戻り値の3つの動作でコピーがとられた時に実行されます
コピーコンストラクタは次ぎのように定義します

class-name(const class-type &obj)

関数名はコンストラクタ同様にクラスの名前と同じです
これに、const指定のコピーもとのオブジェクトの参照を受け取るように引数を指定します
C++では、オブジェクトの初期化時にコピーがとられるとこのコンストラクタを呼び出します
#include <iostream>
using namespace std;

class Kitty {
public:
	char *str;
	Kitty() { str = "Kitty on your lap\n"; }
	Kitty(const Kitty &obj) { str = "Di Gi Gharat\n"; }
} g_obj ;

int main() {
	Kitty obj = g_obj;
	cout << g_obj.str;
	cout << obj.str;
	return 0;
}
Kittyクラスのグローバルオブジェクト g_obj は初期化時にコンストラクタ Kitty() を呼び出します
オブジェクトのコピーの発生時に、コンストラクタが呼び出されないことは先刻も説明しました

上のプログラムでは、宣言時の初期化 Kitty obj = g_obj が main() 関数内で行われています
このとき呼び出されるのがコピーコンストラクタです
コピーコンストラクタの初期化では、メンバ変数 str に "Di Gi Gharat" という文字列を代入しています
結果として、main() 関数の obj.str は Di Gi Gharat を出力します(g_obj.str は Kitty on your lap となる)

ただし、コピーコンストラクタは代入では作用しないので注意してください
main()関数内の obj オブジェクトを次のようにするとコピーコンストラクタは呼び出されません

Kitty obj;
obj = g_obj;

これは、初期化ではなく代入なのでコピーコンストラクタは作用されません

コピーコンストラクタは、関数にオブジェクトを値渡しする時
関数から呼び出しもとにオブジェクトを値渡しする時にも呼び出されます
#include <iostream>
using namespace std;

class Kitty {
public:
	Kitty() { cout << "Kitty on your lap\n"; }
	Kitty(const Kitty &obj) { cout << "Di Gi Gharat\n"; }
};

Kitty getKitty(Kitty obj) {
	return obj;
}

int main() {
	Kitty obj;
	obj = getKitty(obj);
	return 0;
}
このプログラムを実行すると、関数にオブジェクトを渡した時と
戻り値としてオブジェクトを受け取った時の2回、コピーコンストラクタが実行されます

最後に、コピーコンストラクタの実用例をサンプルとしてあげる
下のプログラムでは、関数間の受け渡しなどが行われていないが
メンバ変数 x は動的に割り当てた領域へのポインタとなっている
このメンバ変数の整合性を保つために、コピーコンストラクタを使ってみる

多少長いプログラムになってしまいましたが、コピーコンストラクタの実用性が理解できない方は
以下のようなプログラムを作成し、実行してみたください
#include<iostream>
#include<stdlib.h>
using namespace std;

class Kitty {
	int *x;
public:
	Kitty();
	Kitty(const Kitty &);
	~Kitty();
	void printX();
} g_obj ;

Kitty::Kitty() {
	x = (int *)malloc(5 * sizeof(int ));
	for (int i = 0 ; i < 5 ; i++) *(x + i) = i * 2;
}

Kitty::Kitty(const Kitty &obj) { 
	x = (int *)malloc(5 * sizeof(int ));
	for (int i = 0 ; i < 5 ; i++) *(x + i) = i;
}

Kitty::~Kitty() { free(x); }

void Kitty::printX() {
	for (int i = 0 ; i < 5 ; i++) cout << *(x + i) << " ";
	cout << '\n';
}

int main() {
	Kitty obj = g_obj;
	g_obj.printX();
	obj.printX();
	return 0;
}
このプログラムでは、動的に領域を割り当てる方法としてC言語の malloc() 関数を使用しています
C++では、これよりも簡単で確実な方法がありますが、その方法は後記します



前のページへ戻る次のページへ