オブジェクトの初期化と代入

ライブラリを書いていると、自作のクラスを便利に使えるようにコンストラクタ・コピーコンストラクタ・コピー代入演算子をたくさん用意したくなることがあります。でも、これらが一体いつどこで呼ばれるのか資料が少なかったので、また仕様書を読んでみました。そんなわけで以下メモです。

注:この記事ではunionやPOD・非PODの区別は書いていません。

初期化と代入文

まず大原則として、初期化と代入文は全くの別物として扱われます。例えば、下のコードの3行目は代入文のように見えますが、これは単に初期化の表記に=記号を使っているだけで、代入文ではありません。

T t;		//初期化子のない初期化
T t(1.0);	//直接の初期化
T t = u;	//コピー表記の初期化
t = u;		//代入文

ここではそれぞれの構文の区別のために、コメントに書いた名前で呼びたいと思います。

初期化について

初期化子のない初期化では、クラスの場合はコンストラクタが呼ばれ、それ以外では不確定な値を取ります。

直接の初期化はT t(1.0)のような場合の他に、T(1.0)のような書き方・new・static_cast・メンバ初期化子による初期化時にも行われます。また、コピー表記の初期化はT t = u;のような場合の他に、関数の引数・関数の戻り値・例外送出・例外ハンドリング・{}を使った初期化のときにも行われます。

クラスを初期化するとき、直接の初期化の場合と「コピー表記の初期化で、両方が同じ型であるか、ある型をその派生した型で初期化する場合」に対しては対応するコンストラクタが直接呼ばれます。それ以外のクラスの初期化では、対象の型へのユーザ定義の変換がされた後、対応するコンストラクタが呼ばれます。

非クラスをクラスで初期化する場合には、変換関数が呼ばれます。それ以外の場合は、標準の型変換が行われます。

代入文について

代入文では、クラスへの代入にはコピー代入演算子が使用され、それ以外では暗黙の標準型変換が使用されます。

一時オブジェクト

コンパイラは、型変換や関数の引数の初期化・戻り値のための一時オブジェクトの生成を省略することができます。しかし、コンパイラによって一時オブジェクトの生成が省略されると分かっていたとしても、生成時に使用されるはずのコンストラクタのような、一時オブジェクトの生成に必要な要素を省略することはできません。

まとめ

コピー代入演算子が呼ばれるのは、プログラマが直に代入文を書いたときや、直接演算子を呼んだときぐらいしかありません。それ以外の初期化や言語機能上コピーが必要になる場面では、全てコンストラクタが使われます。