コピーコンストラクタとは
このページではコンストラクタに関する補足的な内容を扱います。
コンストラクタの基本についてはコンストラクタとデストラクタを参照。
まず、クラスから生成したインスタンスは以下の2種類の方法でコピーを行うことが出来ます。
myClass a;
myClass b = a;
myClass a;
myClass b(a);
どちらも直感的に理解できるかと思います。
1つ目の例では、myClassのインスタンスaを同じくmyClassのインスタンスであるbに代入していますね。
変数と同じく値をコピーできると考えて良いでしょう。
2つ目の例ですが、こちらはインスタンスbを生成する際に引数としてインスタンスaを指定しています。
このことから、myClassにはmyClass型の引数を渡すとその値をコピーするコンストラクタが定義されていることがわかります。
基本的なプログラムであればこの程度の知識があれば十分ですが、ここではもう一歩踏み込んで見てみましょう。
2つ目の例でインスタンスをコピーするコンストラクタがあると言いましたが、これをその名の通りコピーコンストラクタと言います。
自明なコピーコンストラクタ
自作のクラスをコピーした場合は、コピーコンストラクタを定義しなくてもコピーを行うことが出来ます。
何故でしょうか。
これはコンパイラ上で自動的にコピーコンストラクタが定義されているからです。
これを自明なコピーコンストラクタと言います。
自明なコピーコンストラクタはクラス内のメンバ変数をそのままコピーするだけのコンストラクタです。
デフォルトコンストラクタに似ていますね。
これによりプログラマーはコピーコンストラクタを意識しなくてもインスタンスのコピーを行うことが出来ます。
コピーコンストラクタを定義する
全てのメンバ変数をそのままコピーしては困る場合には、コピーコンストラクタを定義することが出来ます。
コピーコンストラクタの宣言は以下のようになります。
【クラス名】 { 【クラス名】(const 【クラス名】 &【引数名】); }
クラス名には全て同じ名前が入ります。
同じクラスのインスタンスの参照を引数に持つのがコピーコンストラクタです。
具体例を見てみましょう。
copyconstractor_sample1.cpp
myClass { myClass(const myClass &myClass); }
定義自体は他のメンバ関数と同様です。
これを使えば、あるメンバはコピーしたいが他のメンバは0で初期化したい、といった状況に対応できます。
コピーコンストラクタとポインタ
自明なコピーコンストラクタでは不都合が起きる例として、あるメンバ変数を指すポインタ型のメンバ変数を持っているときがあります。
インスタンスをコピーした際に、ポインタ型のメンバにはコピー前のメンバ変数のアドレスがコピーされます。
その為、新規で生成したインスタンスとコピーして出来たインスタンスでは挙動が異なります。
イメージしにくいので実例を見てみましょう。
copyconstractor_sample2.cpp
myClass { int mem; int *pmem; myClass(); }
int型のメンバmemとそのアドレスを示すポインタpmemを持っています。
コンストラクタでmemは任意の値、pmemhはmemのアドレスで初期化されます。
myClass a;
myClass b = a;
インスタンスaを生成し、インスタンスbにインスタンスaをコピーします。
コピーコンストラクタを定義していないので自明なコピーコンストラクタが呼び出されます。
これによってb.memにはa.mem、b.pmemにはa.pmemが代入されます。
これが問題になります。
b.pmemにはb.memのアドレスを保持して欲しいところですが実際に入っているのはa.memのアドレスです。
なのでb.pmemを使ってb.memを変更しようとするとa.memが変わってしまいます。
このような場合はコピーコンストラクタを自身で定義し、b.pmemをb.memのアドレスで定義する必要があります。