デフォルトで42
に初期化されるB
を持つクラスmember
があるとします。このクラスは、そのmember
の値を出力する方法を知っています(コンストラクターで出力します):
struct B
{
B() : member(42) { printMember(); }
void printMember() const { std::cout << "value: " << member << std::endl; }
int member;
};
次に、クラスA
を追加します。このクラスは、B
へのconst参照を受け取り、その値を出力するようにB
に要求します。
struct A
{
A(const B& b) { b.printMember(); }
};
最後に、Aggregate
とA
を集約する別のクラスB
を追加します。トリッキーな部分は、タイプa
のオブジェクトA
がオブジェクトb
タイプB
の前に宣言されますが、その後a
はb
への参照を使用して初期化されます:
struct Aggregate
{
A a;
B b;
Aggregate() : a(b) { }
};
Aggregate
の作成の出力を検討してください(A
とB
のコンストラクターとデストラクタの両方にいくつかのロギングを追加しました)( Try it! ):
a c'tor
value: 0
b c'tor
value: 42
b d'tor
a d'tor
a
の(まだ有効ではない)インスタンスへの参照でb
を初期化することは無効であり、したがって、これは未定義の動作であると想定するのは正しいでしょうか?
初期化の順序を認識しています。これは私が苦労するものです。 I knowb
はまだ構築されていないが、thinkb
の将来のアドレスは、b
が構築される前であっても決定できることを知っている。したがって、私はそこにあると仮定しました気づかないルールがあるかもしれませんコンパイラがb
の構築などの前にb
sメンバーをデフォルトで初期化できるようにします。 (最初に出力された値が0
(int
のデフォルト値)ではなく、ランダムに見えるものであれば、より明白でした)。
この答え 区別する必要があることを理解するのに役立ちました
はい、そうです [〜#〜] ub [〜#〜] ですが、構築されていないオブジェクトへの参照を単に保存するのとは異なる理由で。
クラスメンバーの構築は、クラス内での出現順に行われます。 B
のアドレスは変更されませんが、技術的には 参照を保存できます ですが、@ StoryTellerが指摘したように、コンストラクターでb.printMember()
を呼び出しますまだ構築されていないb
は間違いなくUBです。
クラスメンバーの初期化の順序は次のとおりです。
CPP標準(N4713)から、関連する部分が強調表示されています:
15.6.2ベースとメンバーの初期化[class.base.init] ...
13非委任コンストラクターでは、初期化は次の順序で進行します。
(13.1)—最初に、最も派生したクラス(6.6.2)のコンストラクターに対してのみ、仮想基底クラスは、深さ優先の左から右へのトラバースに現れる順序で初期化されます。基本クラスの非循環有向グラフ。ここで、「左から右」は、派生クラスbase-specifier-list内の基本クラスの出現順序です。
(13.2)—次に、直接の基本クラスは、mem-initializerの順序に関係なく、base-specifier-listに表示される宣言順に初期化されます。
(13.3)— 非静的データメンバーは、クラス定義で宣言された順序で初期化されます(メモリ初期化子の順序に関係なく)。
(13.4)—最後に、コンストラクター本体の複合ステートメントが実行されます。
[注:宣言の順序は、初期化の逆の順序でベースサブオブジェクトとメンバーサブオブジェクトを確実に破棄するために義務付けられています。 —注を終了]