コンストラクタとメンバ初期化子リスト
コンストラクタは、そのクラス型のオブジェクトを初期化するために使用される、クラスの特別な非静的メンバ関数です。
クラスのコンストラクタの定義において、メンバ初期化子リストは、直接のおよび仮想基底の部分オブジェクトおよび非静的データメンバに対する初期化子を指定します。 (std::initializer_list と混同しないでください。)
構文
コンストラクタは以下の形式のメンバ関数宣言子を用いて宣言されます。
class-name ( parameter-list(オプション) ) except-spec(オプション) attr(オプション)
|
(1) | ||||||||
class-name は現在のクラス (またはクラステンプレートの現在の実体化) を表さなければならないか、または、名前空間スコープまたはフレンド宣言で宣言されたときは、修飾付きクラス名でなければなりません。
コンストラクタ宣言の decl-specifier-seqで使用できる指定子は friend、 inline、 constexpr (C++11以上)、 consteval (C++20以上)、および explicit だけです (特に、戻り値の型を指定できません)。 cv 修飾子および参照修飾子も使用できないことに注意してください。 構築中のオブジェクトの const および volatile の意味論は最も派生したコンストラクタが完了するまで適用されません。
コンストラクタの関数定義の本体は、複文の開き波括弧の前に、メンバ初期化子リストを含むことができます。 その構文はコロン文字 : の後にコンマ区切りの1個以上の member-initializers が続いたもので、そのそれぞれは以下の構文を持ちます。
class-or-identifier ( expression-list(オプション) )
|
(1) | ||||||||
| class-or-identifier brace-init-list | (2) | (C++11以上) | |||||||
parameter-pack ...
|
(3) | (C++11以上) | |||||||
| class-or-identifier | - | 非静的データメンバ、直接または仮想の基底、または (委譲コンストラクタの場合は) そのクラス自身を表す任意の識別子、クラス名、または decltype 式
|
| expression-list | - | 基底またはメンバのコンストラクタに渡す引数のコンマ区切りのリスト (空でも構いません) |
| braced-init-list | - | コンマ区切りの初期化子およびネストした波括弧初期化子リストの波括弧で囲まれたリスト |
| parameter-pack | - | 可変長引数のパラメータパックの名前 |
struct S {
int n;
S(int); // コンストラクタの宣言。
S() : n(7) {} // コンストラクタの定義。
// 「: n(7)」が初期化子リストです。
// 「: n(7) {}」が関数の本体です。
};
S::S(int x) : n{x} {} // コンストラクタの定義。 「: n{x}」が初期化子リストです。
int main()
{
S s; // S::S() を呼びます。
S s2(10); // S::S(int) を呼びます。
}
説明
コンストラクタは名前を持たず、直接呼ぶことはできません。 それらは初期化が行われるときに呼ばれ、初期化のルールに従って選択されます。 explicit 指定子のないコンストラクタは変換コンストラクタです。 constexpr 指定子付きのコンストラクタはその型を LiteralType にします。 引数なしで呼ぶことができるコンストラクタはデフォルトコンストラクタです。 同じ型の別のオブジェクトを引数として取るコンストラクタはコピーコンストラクタまたはムーブコンストラクタです。
コンストラクタの関数本体を形成する複文が実行を開始する前に、すべての直接の基底、仮想基底、非静的データメンバの初期化が完了します。 メンバ初期化子リストはそれらのオブジェクトの非デフォルト初期化を指定できる場所です。 参照や const 修飾された型のメンバのようなデフォルト初期化できないメンバに対しては、メンバ初期化子を指定しなければなりません。 無名共用体またはメンバ初期化子を持たない変種メンバに対しては初期化は行われません。
class-or-identifier が仮想基底クラスを表す初期化子は、構築中のオブジェクトの最も派生したクラスでないあらゆるクラスのコンストラクタの実行中は無視されます。
expression-list または brace-init-list に現れる名前はコンストラクタのスコープで評価されます。
class X {
int a, b, i, j;
public:
const int& r;
X(int i)
: r(a) // X::a を参照するように X::r を初期化します。
, b{i} // 引数 i の値に X::b を初期化します。
, i(i) // 引数 i の値に X::i を初期化します。
, j(this->i) // X::i の値に X::j を初期化します。
{ }
};
メンバ初期化子から投げられる例外は関数 try ブロックで処理できます。
メンバ関数 (仮想メンバ関数を含みます) はメンバ初期化子から呼ぶことができますが、その時点ですべての直接の基底が初期化されていなければ動作は未定義です。
仮想呼び出しに対しては (基底が初期化されている場合)、コンストラクタおよびデストラクタからの仮想呼び出しのルールと同じルールが適用されます。 仮想メンバ関数は *this の動的な型が構築中のクラスであるかのように動作し (動的ディスパッチは継承階層を伝って降りず)、純粋仮想メンバ関数の仮想呼び出しは未定義です (しかし静的な呼び出しはそうではありません)。
|
非静的データメンバがデフォルトメンバ初期化子を持ち、メンバ初期化子リストにも現れている場合は、メンバ初期化子が実行され、デフォルトメンバ初期化子は無視されます。 struct S {
int n = 42; // デフォルトメンバ初期化子。
S() : n(7) {} // n を (42 ではなく) 7 に設定します。
};
|
(C++11以上) |
|
参照メンバをメンバ初期化子リスト内の一時オブジェクトに束縛することはできません。 struct A {
A() : v(42) { } // エラー。
const int& v;
};
ノート: 同じルールがデフォルトメンバ初期化子に適用されます。 |
(C++14以上) |
委譲コンストラクタクラス自身の名前がメンバ初期化子リストに class-or-identifier として現れた場合、そのリストはそのメンバ初期化子ひとつのみで構成されなければなりません。 そのようなコンストラクタは委譲コンストラクタと言い、その初期化子リストの唯一のメンバによって選択されたコンストラクタはターゲットコンストラクタです。 この場合、ターゲットコンストラクタがオーバーロード解決によって選択されて最初に実行され、その後、制御が委譲コンストラクタに戻り、その本体が実行されます。 委譲コンストラクタは再帰できません。 class Foo {
public:
Foo(char x, int y) {}
Foo(int y) : Foo('a', y) {} // Foo(int) は Foo(char,int) に委譲します。
};
継承コンストラクタusing 宣言を参照してください。 |
(C++11以上) |
初期化の順序
リスト内のメンバ初期化子の順序は意味を持ちません。 初期化の実際の順序は以下の通りです。
(ノート: もし初期化子の順序が異なるコンストラクタのメンバ初期化子リスト内の出現順によって制御されていたならば、デストラクタは破棄の順序が構築の順序の逆であることを保証できなかったでしょう。)
例
#include <fstream>
#include <string>
#include <mutex>
struct Base {
int n;
};
struct Class : public Base
{
unsigned char x;
unsigned char y;
std::mutex m;
std::lock_guard<std::mutex> lg;
std::fstream f;
std::string s;
Class ( int x )
: Base { 123 }, // 基底クラスを初期化します。
x ( x ), // x (メンバ) が x (引数) で初期化されます。
y { 0 }, // y が 0 に初期化されます。
f{"test.cc", std::ios::app}, // これは m と lg が初期化された後に行われます。
s(__func__), // 初期化子リストはコンストラクタの一部であるため、 __func__ が利用可能です。
lg ( m ), // lg が m を使用します。 m はすでに初期化されています。
m{} // m はたとえここで最後に現れたとしても lg より前に初期化されます。
{} // 空の複文。
Class ( double a )
: y ( a+1 ),
x ( y ), // x は y より前に初期化され、その値はここでは不定です。
lg ( m )
{} // 基底クラスのコンストラクタはリストに現れておらず、デフォルト初期化されます
// (Base () が使用された場合と同じではありません (その場合は値初期化されます))。
Class()
try // 関数 try ブロックは関数の本体 (初期化子リストを含む) より前に始まります。
: Class( 0.0 ) // 委譲コンストラクタ。
{
// ...
}
catch (...)
{
// 初期化中に例外が発生した
}
};
int main() {
Class c;
Class c1(1);
Class c2(0.1);
}
欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
| DR | 適用先 | 発行時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 1696 | C++14 | reference members could be initialized to temporaries (whose lifetime would end at the end of ctor) | such init is ill-formed |
参考文献
- C++11 standard (ISO/IEC 14882:2011):
- 12.1 Constructors [class.ctor]
- 12.6.2 Initializing bases and members [class.base.init]
- C++98 standard (ISO/IEC 14882:1998):
- 12.1 Constructors [class.ctor]
- 12.6.2 Initializing bases and members [class.base.init]