C++ デフォルトコンストラクタの自動生成と暗黙的な呼び出し
C++のデフォルトコンストラクタについて整理します。デフォルトコンストラクタの自動生成、自動生成されない場合、暗黙的に呼び出される場合を取り上げます。補足的にオブジェクトの初期化式やクラスメンバーの初期化リストの書き方も取り上げます。
デフォルトコンストラクタの自動生成
C++ではデフォルトコンストラクタは、コンストラクタを書かなければ自動生成されます。しかし、他のコンストラクタを一つでも書くと自動生成されません。そのため、他のコンストラクタを書き、うっかりデフォルトコンストラクタを書き忘れると下記の式はコンパイルすることができません。
ClassName VariableName = ObjectName;
この式は、左辺においてオブジェクトがデフォルトコンストラクタにより初期化・生成された後に、Operator=()で右辺のオブジェクトを左辺のオブジェクトに代入(コピー)することになります(参照:C++ クラスの代入演算子 代入に必要なコンストラクタ)。そのため、デフォルトコンストラクタがなければこの式はコンパイルエラーになります。
クラスの定義の際に書かなくても自動生成されるのは、デフォルトコンストラクタだけではなく、デフォルトデストラクタ、デフォルトコピーコンストラクタ、デフォルト代入演算子関数も自動生成されます。
デフォルトコンストラクタが暗黙的に呼び出される場合
上記の式では、左辺でデフォルトコンストラクタが暗黙的に呼び出されました。その他にも以下に列挙するようにデフォルトコンストラクタが暗黙的に呼び出される場合があるので気を付ける必要があります。
クラスの継承の場合で、スーパークラスのコンストラクタを初期化リストで明示的に呼び出さない場合には、デフォルトコンストラクタが暗黙的に呼び出されます。そのため、スーパークラスに引数付きのコンストラクタをオーバーロードして、デフォルトコンストラクタがなくなった場合は、サブクラスの初期化リストで明示的にスーパークラスに実装した引数付きコンストラクタを呼び出さなければ、エラーとなります。
クラスのメンバについても初期化リストで明示的にコンストラクタを呼び出さない場合には、デフォルトコンストラクタが暗黙的に呼び出されます。そのため、引数付きコンストラクタをオーバーロードしたクラスを、他のクラスのメンバにした場合に、初期化リストで実装したコンストラクタで明示的に初期化しなければコンパイルエラーになります。
ちなみに、初期化リストでメンバを初期化しないでコンストラクタ内で初期値を設定するということは、デフォルトコンストラクタで初期化された後に、コンストラクタ内で代入処理をすることであり効率が落ちます。これは最初に挙げた式、
ClassName VariableName = ObjectName;
のような二段階の処理になります。また、初期化の順序はスーパークラスのコンストラクトを始めにして順番に初期化すると問題が生じにくくなります。基本データ型の場合には、関数内の自動変数と同じく初期化しなければ適当な値が入ります。
オブジェクトの初期化式やクラスメンバーの初期化リストの書き方
C++のオブジェクトの初期化式は、引数がない場合と引数がある場合によって、
ClassName VariableName; ClassName VariableName(AnyArguments);
と「()」の有無が異なる形式で書きます。
問題は、引数がないのに、
ClassName VariableName();
と書くと仮引数のないVariableName関数のプロトタイプ宣言とみなされてしまいバグになることです。
引数がない場合は括弧を取って、
ClassName VariableName;
と宣言すれば、デフォルトコンストラクタが正しく呼び出され初期化されます。初期値のない基本データ型の宣言と同じと考えれば良いと思います。
初期値のある場合の基本データ型の初期化は通常「=」で行いますが、引数のあるオブジェクトの初期化式と同様に「()」を用いて初期化することもできます。
int a = 0; int a(0); //こちらもOK
クラスの基本データ型のメンバの初期化は、初期化リストでこの「()」を用いた形式を使って行います。以上のようにC++では基本データ型とクラスオブジェクト型で初期化形式の整合性が図られているようです。
参考サイト
#pragma twice 338 Version 16.11 様々なコンストラクタ, #pragma twice, 1999-2007
第16章 派生と構築, ロベール, 2001
代入より初期化, C++とプログラム全般
初期化指定子, ソフトウェア インフォーメイション センター IBM