web-dev-qa-db-ja.com

このC ++メンバーの初期化動作は適切に定義されていますか?

デフォルトで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(); }
};

最後に、AggregateAを集約する別のクラスBを追加します。トリッキーな部分は、タイプaのオブジェクトAがオブジェクトbタイプBの前に宣言されますが、その後abへの参照を使用して初期化されます:

struct Aggregate
{
  A a;
  B b;

  Aggregate() : a(b) { }
};

Aggregateの作成の出力を検討してください(ABのコンストラクターとデストラクタの両方にいくつかのロギングを追加しました)( 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の構築などの前にbsメンバーをデフォルトで初期化できるようにします。 (最初に出力された値が0intのデフォルト値)ではなく、ランダムに見えるものであれば、より明白でした)。


この答え 区別する必要があることを理解するのに役立ちました

  • 参照のバインド初期化されていないオブジェクト(有効)に
  • 参照によるアクセス初期化されていないオブジェクト(未定義)
21
simon

はい、そうです [〜#〜] ub [〜#〜] ですが、構築されていないオブジェクトへの参照を単に保存するのとは異なる理由で。

クラスメンバーの構築は、クラス内での出現順に行われます。 Bのアドレスは変更されませんが、技術的には 参照を保存できます ですが、@ StoryTellerが指摘したように、コンストラクターでb.printMember()を呼び出しますまだ構築されていないbは間違いなくUBです。

23
Dev Null

クラスメンバーの初期化の順序は次のとおりです。

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)—最後に、コンストラクター本体の複合ステートメントが実行されます。
[注:宣言の順序は、初期化の逆の順序でベースサブオブジェクトとメンバーサブオブジェクトを確実に破棄するために義務付けられています。 —注を終了]

1
P.W