|
書籍転載
文法からはじめるプログラミング言語Microsoft Visual C++入門
C++のクラスをマスターしよう(後編)
―― 第10章 クラス〜オブジェクト指向プログラミング(中編) ――
WINGSプロジェクト 矢吹 太朗(監修 山田 祥寛)
2010/06/02 |
|
本コーナーは、日経BPソフトプレス発行の書籍『文法からはじめるプログラミング言語Microsoft Visual C++入門』の中から、特にInsider.NET読者に有用だと考えられる章や個所をInsider.NET編集部が選び、同社の許可を得て転載したものです。基本的に元の文章をそのまま転載していますが、レイアウト上の理由などで文章の記述を変更している部分(例:「上の図」など)や、図の位置などを本サイトのデザインに合わせている部分が若干ありますので、ご了承ください。『文法からはじめるプログラミング言語Microsoft Visual C++入門』の詳細は「目次情報ページ」もしくは日経BPソフトプレスのサイトをご覧ください。 |
ご注意:本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。 |
●10.1.10 コピーコンストラクタ
あるオブジェクトをコピーして新しいオブジェクトを作る際には、コピーコンストラクタを利用します。デストラクタの場合と同様で、フリーストア上のオブジェクトを保持していない場合には、コンパイラが自動生成するコピーコンストラクタで十分です。
次のようなクラスA、Bを考えます。Aオブジェクトa1は内部にBオブジェクトを持っています。a1をコピーして新しいオブジェクトa2を作るコピーコンストラクタは、a1.idだけでなく、a1.bやa1.b.idもコピーしなければなりません。コンパイラが自動生成するコピーコンストラクタは、オブジェクトを再帰的にコピーすることでこのタスクを実行します。
#include <iostream>
using namespace std;
struct B
{
int id;
};
struct A
{
int id;
B b;
};
int main()
{
A a1;
a1.id=10;
a1.b.id=11; //a1のメンバbのフィールドidを設定
//a1をコピーして新しいオブジェクトa2を作る
A a2=a1; //A a2(a1); //でもよい
//コピーされたことの確認
cout<<a2.id<<endl; //出力値: 10
cout<<a2.b.id<<endl; //出力値: 11
//a1とa2が同じオブジェクトではないことの確認
a1.id=20; //a1のフィールドや
a1.b.id=22; //a1.bのフィールドを変更しても
cout<<a2.id<<endl; //出力値: 10(a2.idは変わっていない)
cout<<a2.b.id<<endl; //出力値: 11(a2.b.idは変わっていない)
}
|
|
[サンプル]10-copyconstructor1.cpp |
このコードでは、a1.idの値を10に、a1.b.xの値を11にして、a1をコピーして新しいオブジェクトa2を作っています。a2.idやa2.b.idが元の値と同じであることと、a1.idやa1.b.idを変更してもa2.idやa2.b.idは変わっていないことから、a1とa2、a1.bとa2.bは同じオブジェクトではない、つまり正しくコピーされたことがわかります。
しかし、図10-4の右側のように、メンバとしてポインタを持っていて、そのポインタが指すオブジェクトもコピーしたい場合には、自動生成されるコピーコンストラクタは役に立ちません*6。ポインタの値(アドレス)がコピーされるだけなので、新しいポインタも同じオブジェクトを指すからです。
*6 アドレスをコピーしたい場合には問題ありません。これは代入演算子(後述)の場合にもあてはまります。 |
|
図10-4 既定のコピーコンストラクタでうまくいく例とうまくいかない例 |
ポインタが指すオブジェクトも複製したいような場合には、コピーコンストラクタを自分で定義しなければなりません。コピーコンストラクタは次のように定義します(宣言と定義を分けることもできます)。rhsはright-hand sideの略で右辺を表します。A a1=a2のように書いたときのrhsはa2です。
クラス名(const クラス名& rhs) : コンストラクタ初期化子リスト
{
文
}
|
|
[構文]コピーコンストラクタの定義 |
コピーコンストラクタはすべてのフィールドをコピーしなければなりません。ここでコピーすべきなのはidとpBが指すオブジェクトつまり*pBです。idをコピーするのは簡単で、id(rhs.id)という初期化子を書けばよいでしょう。この初期化子によって、新しいAオブジェクトのidが、元のオブジェクト(rhs)のidと同じ値になります。同じようにpB(rhs.pB)としても、rhs.pBの値つまりオブジェクトのアドレスがコピーされるだけで、オブジェクト自体はコピーされません。pBが指すオブジェクトをコピーするためには、新しいBオブジェクトを作り、そのフィールドidを元のオブジェクトのフィールドidつまりrhs.pB->idと同じにしなければなりません。
次のようなコピーコンストラクタで目的は達成できます。
#include <iostream>
using namespace std;
struct B
{
int id;
};
struct A
{
int id;
B* pB;
//Bオブジェクトを生成するコンストラクタ
A() : pB(new B) {}
//コピーコンストラクタ
A(const A& rhs) : id(rhs.id), pB(new B) { pB->id=rhs.pB->id; }
~A() { delete pB; }
};
int main()
{
A a1;
a1.id=10;
a1.pB->id=11;
A a2=a1; //コピーコンストラクタ
//コピーされたことの確認
cout<<a2.id<<endl; //出力値: 10
cout<<a2.pB->id<<endl; //出力値: 11
//a1とa2が同じオブジェクトではないことの確認
a1.id=20; //a1のフィールドや
a1.pB->id=22; //*a1.pBのフィールドを変更しても
cout<<a2.id<<endl; //出力値: 10(a2.idは変わっていない)
cout<<a2.pB->id<<endl; //出力値: 11(a2.pB->idは変わっていない)
}
|
|
[サンプル]10-copyconstructor2.cpp |
●10.1.11 暗黙の変換
引数の数が1つのコンストラクタは、暗黙の変換も定義します。暗黙の変換によって、オブジェクトを初期化するための記述を次のように単純にすることができます。
#include <iostream>
using namespace std;
struct A
{
int x;
A(int x) : x(x) { cout<<"A(int x) is called.\n"; };
};
int main()
{
A a=5; //A a(5);と解釈される。
//出力値:A(int x) is called.
} |
|
[サンプル]10-explicit1.cpp |
このコードでは、A a=5;としてAオブジェクトを生成しようとしていますが、5はAオブジェクトではなくint型のリテラルなので、前項で紹介したコピーコンストラクタは使われません。この記述は、A a(5);と解釈され、コンストラクタA(int x)が呼ばれます。これが暗黙の変換です。3.4.1項で紹介したように、std::stringオブジェクトをstring str("Hello World!");ではなくstring str="Hello World!";として生成できるのも暗黙の変換のおかげです。
暗黙の変換は便利な機能ですが、プログラマが予期しない場所で起こる危険もあります。暗黙の変換が起こらないようにするためには、次のようにキーワードexplicitを付けてコンストラクタを宣言します。
#include <iostream>
using namespace std;
struct A
{
int x;
//暗黙の型変換を禁止する
explicit A(int x) : x(x) { cout<<"A(int x) is called.\n"; };
};
int main()
{
A a=5; //エラー
}
|
|
[サンプル]10-explicit2.cpp |
Insider.NET 記事ランキング
本日
月間