ホームに戻る
出典 :
関連 :
目次 :
コピーコンストラクタとは
クラス(または構造体、共用体)インスタンスを初期化する際に、既存の同型インスタンスを引数に取るコンストラクタのこと。
インスタンスへの代入(コピー)時に実行される。
デフォルトコピーコンストラクタ
コピーコンストラクタを定義せずにインスタンスの代入を行うと、メンバ変数がすべてコピーされる。
(Cにおける構造体代入と同様。)
これは、クラス定義時にデフォルトのコピーコンストラクタが自動生成されることによる。
// クラス定義
class SimpleClass
{
private:
int number;
std::string name;
public:
// コンストラクタ
SimpleClass() { number = 0; }
// コンストラクタ
SimpleClass(int n, char *s)
{
number = n;
name = s;
}
// (コピーコンストラクタは定義されていない)
int getNumber() { return number; }
void setNumber(int n) { number = n; }
std::string getName() { return name; }
void setName(char* s) { name = s; }
};
void main()
{
// SimpleClass 型のインスタンス sc1 生成
// (通常のコンストラクタ実行)
SimpleClass sc1(1, "John");
std::cout << "(sc1) number : " << sc1.getNumber() << std::endl;
std::cout << "(sc1) name : " << sc1.getName() << std::endl;
// SimpleClass 型のインスタンス sc2 生成
// (コピーコンストラクタ実行)
SimpleClass sc2(sc1);
std::cout << "(sc2) number : " << sc1.getNumber() << std::endl;
std::cout << "(sc2) name : " << sc1.getName() << std::endl;
// SimpleClass 型のインスタンス sc3 生成
// (コピーコンストラクタ実行)
SimpleClass sc3 = sc1;
std::cout << "(sc3) number : " << sc1.getNumber() << std::endl;
std::cout << "(sc3) name : " << sc1.getName() << std::endl;
}
実行結果 :
(sc1) number : 1
(sc1) name : John
(sc2) number : 1
(sc2) name : John
(sc3) number : 1
(sc3) name : John
上記の例では、SimpleClass 型のインスタンス sc1 を用いて、同じ型のインスタンス sc2 、sc3 を生成している。
( sc2 と sc3 で構文は異なるが、機能は等価である。)
このとき、SimpleClass 型はコピーコンストラクタが定義されていないためデフォルトのコピーコンストラクタが呼ばれ、
結果、sc2 および sc3 のメンバ変数の値は sc1 と等しくなる。
何故コピーコンストラクタが必要か
特にポインタ型変数を含む場合、デフォルトのコピーコンストラクタを用いることで問題が生じることがある。
// ポインタ変数を有する PointerClass 定義
class PointerClass
{
int* pointer; //< ポインタ変数
public:
// コンストラクタ
PointerClass(int* p = 0) { pointer = p; }
// (コピーコンストラクタは定義されていない)
int *get() { return pointer; }
};
void main()
{
int num = 10;
// PointerClass 型インスタンス pc1 生成
PointerClass pc1(&num);
// PointerClass 型インスタンス pc2 生成
// (デフォルトコピーコンストラクタによるコピー)
PointerClass pc2(pc1);
std::cout << "pc1値 : " << *(pc1.get()) << std::endl;
std::cout << "pc1ポインタ : " << pc1.get() << std::endl;
std::cout << "pc2値 : " << *(pc2.get()) << std::endl;
std::cout << "pc2ポインタ : " << pc2.get() << std::endl;
// pc2から値を書き換え
*(pc2.get()) = 20;
std::cout << "pc1値 : " << *(pc1.get()) << std::endl;
std::cout << "pc1ポインタ : " << pc1.get() << std::endl;
std::cout << "pc2値 : " << *(pc2.get()) << std::endl;
std::cout << "pc2ポインタ : " << pc2.get() << std::endl;
}
実行結果 :
pc1値 : 10
pc1ポインタ : 00CFFA38
pc2値 : 10
pc2ポインタ : 00CFFA38
pc1値 : 20 //< pc2 に加えた変更が波及している
pc1ポインタ : 00CFFA38
pc2値 : 20
pc2ポインタ : 00CFFA38
上記は、ポインタ型変数を有するクラスの例である。
デフォルトコピーコンストラクタはメンバ変数を全てコピーするため、pc1 の pointer (アドレス値)と pc2 の pointer は等しくなる。
同一のアドレスを両者で共有しているため、一方で pointer の指し示す値を変更すると他方にも波及してしまう。
// ポインタ変数を有する PointerClass 定義
class PointerClass
{
int* pointer; //< ポインタ変数
public:
// コンストラクタ
PointerClass(int num)
{
pointer = new int(num);
}
// (コピーコンストラクタは定義されていない)
int *get() { return pointer; }
private:
// デストラクタ
~PointerClass()
{
delete pointer;
}
};
void main()
{
// PointerClass 型インスタンス pc1 生成
PointerClass pc1(10);
// PointerClass 型インスタンス pc2 生成
// (デフォルトコピーコンストラクタによるコピー)
PointerClass pc2(pc1);
:
// ここで実行時エラーとなる
}
上記は、ポインタ型変数に動的メモリを割り当てている例である。
main() 関数を抜ける際に、ローカル変数である pc1 、pc2 は破棄されるが、その際にデストラクタによって動的メモリの破棄が行われる。
しかし先の例と同様 pc1 と pc2 の pointer (アドレス値)は等しいため、一方で解放したメモリを他方でも解放しようとし、エラーとなる。
コピーコンストラクタを定義し、ポインタ変数の値を共用しないようにすることで、これらの事態を防ぐことができる。
コピーコンストラクタの定義
// ポインタ変数を有する PointerClass 定義
class PointerClass
{
int* pointer; //< ポインタ変数
public:
// コンストラクタ
PointerClass(int num)
{
pointer = new int(num);
}
// コピーコンストラクタ
PointerClass(const PointerClass& pc)
{
pointer = new int( *(pc.get()) );
}
int *get() { return pointer; }
private:
// デストラクタ
~PointerClass()
{
delete pointer;
}
};
void main()
{
// PointerClass 型インスタンス pc1 生成
PointerClass pc1(10);
// PointerClass 型インスタンス pc2 生成
// (コピーコンストラクタによるコピー)
PointerClass pc2(pc1);
:
}
コピーコンストラクタは、自身と同型のインスタンスの参照を引数として受け取る。
(引数を誤って更新しないよう、const 修飾をするのが望ましい。参照型についてはここを参照。)
コピーコンストラクタ内でメモリの確保を新たに行っているため、pc1 と pc2 の pointer (アドレス値)は異なる値となる。
(ただし、 pointer の指し示す値は等しい。)
関数の引数とコピーコンストラクタ
関数の引数にオブジェクトを指定した場合もオブジェクトのコピーが作成されるため、コピーコンストラクタが呼ばれる。
ただ、関数にオブジェクトをそのまま渡すことはオーバーヘッドの増大を招くため、そもそも避けられるべきである。
はしがき
コピーコンストラクタの管理は煩雑となりがちだが、参照渡しを用いるなど、コピーコンストラクタに依存しない設計とすることも可能である。
コピーコンストラクタが必要となるのはポインタ変数を含む場合が多い。ポインタ自体、扱いが煩雑で危険を伴うものであるため、
ポインタの使用を避けることでコピーコンストラクタを削減するとともに、コードの安全性を高めることができる。