名前空間
変種
操作

コンストラクタとメンバ初期化子リスト

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス宣言
初期化子リスト
this ポインタ
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

コンストラクタは、そのクラス型のオブジェクトを初期化するために使用される、クラスの特別な非静的メンバ関数です。

クラスのコンストラクタの定義において、メンバ初期化子リストは、直接のおよび仮想基底の部分オブジェクトおよび非静的データメンバに対する初期化子を指定します。 (std::initializer_list と混同しないでください。)

目次

[編集] 構文

コンストラクタは以下の形式のメンバ関数宣言子を用いて宣言されます。

class-name ( parameter-list(オプション) ) except-spec(オプション) attr(オプション) (1)

class-name は現在のクラス (またはクラステンプレートの現在の実体化) を表さなければならないか、または、名前空間スコープまたはフレンド宣言で宣言されたときは、修飾付きクラス名でなければなりません。

コンストラクタ宣言の decl-specifier-seqで使用できる指定子は friendinlineexplicit および constexpr だけです (特に、戻り値の型を指定できません)。 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以上)
1) 直接初期化、または expression-list が空の場合は 値初期化を用いて、 class-or-identifier によって表される基底またはメンバを初期化します。
2) リスト初期化 (これはリストが空の場合は値初期化、集成体を初期化するときは集成体初期化になります) を用いて、 class-or-identifier によって表される基底またはメンバを初期化します。
3) パック展開を用いて、複数の基底を初期化します。
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以上)

[編集] 初期化の順序

リスト内のメンバ初期化子の順序は意味を持ちません。 初期化の実際の順序は以下の通りです。

1) そのコンストラクタが最も派生したクラスに対するものの場合、仮想基底クラスが仮想基底宣言の深さ優先で左から右に巡回したときの出現順で初期化されます (左から右は基底指定子リスト内の出現順を指しています)。
2) その後、直接の基底クラスがこのクラスの基底指定子リスト内に現れる左から右の順序で初期化します。
3) その後、非静的データメンバがクラス定義内の宣言順で初期化されます。
4) 最後に、コンストラクタの本体が実行されます。

(ノート: もし初期化子の順序が異なるコンストラクタのメンバ初期化子リスト内の出現順によって制御されていたならば、デストラクタは破棄の順序が構築の順序の逆であることを保証できなかったでしょう。)

[編集]

#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]