web-dev-qa-db-ja.com

C ++ 11メンバー初期化子リストとクラス内初期化子?

C++ 11でオブジェクトメンバー変数を初期化するこれらの方法の違いは何ですか?別の方法はありますか?どちらの方が優れていますか(パフォーマンス)?:

class any {
  public:
    obj s = obj("value");
    any(){}
};

または

class any {
  public:
    obj s;
    any(): s("value"){}
};

ありがとう。

35
Ahmed T. Ali

いいえ、これらは同じではないです。

これらの違いはdirect-initialization vs. copy-initializationに適用されるものと同じです。

§12.6.2[class.base.init]:

  1. mem-initializerexpression-listまたはbraced-init-listは、指定されたサブオブジェクトを初期化するために使用されます(または、 direct-initialization。 [...]の8.5の初期化ルールによる委任コンストラクター、完全なクラスオブジェクト)

  2. 非委任コンストラクターでは、特定の非静的データメンバーまたは基本クラスがmem-initializer-idで指定されていない場合(mem-initializerがない場合を含む-listコンストラクターにctor-initializer)がなく、エンティティーが抽象クラス(10.4)の仮想基本クラスではないため、

    —エンティティがbrace-or-equal-initializerを持つ非静的データメンバーである場合、エンティティは初期化されます8.5で指定;

§8.5[dcl.init]:

  1. フォームで発生する初期化

    T x = a;

同様に、引数の受け渡し、関数の戻り、例外のスロー(15.1)、例外の処理(15.3)、および集約メンバーの初期化(8.5.1)コピー初期化と呼ばれます

member-initializer-listの非静的データメンバーの初期化は、direct-initializationのルールに従います。このルールは、移動/コピーする必要のある中間一時を作成しません。 (copy-elisionを指定せずにコンパイルした場合)、データメンバのタイプは(コピーが省略されていても)コピー/移動可能である必要はありません。さらに、direct-initializationは明示的なコンテキストを導入しますが、copy-initializationは非明示的です(初期化に選択されたコンストラクターがexplicitの場合、プログラムはコンパイルされません)。

つまり、objが次のように宣言されている場合、obj s = obj("value");構文はコンパイルされません。

_struct obj
{
    obj(std::string) {}
    obj(const obj&) = delete;
};
_

または:

_struct obj
{
    obj(std::string) {}
    explicit obj(const obj&) {}
};
_

より具体的な例として、以下はコンパイルしません:

_struct any
{
   std::atomic<int> a = std::atomic<int>(1); // ill-formed: non-copyable/non-movable
   std::atomic<int> b = 2; // ill-formed: explicit constructor selected
};
_

これは:

_struct any
{
    std::atomic<int> a;
    std::atomic<int> b{ 2 };
    any() : a(1) {}
};
_

どちらの方法が優れている(パフォーマンス)?

copy-elisionを有効にすると、両方のパフォーマンスが同じになります。 copy-elisionを無効にすると、copy-initialization構文が使用されている場合(obj s = obj("value");はの一つ)。


別の方法はありますか?

brace-or-equal-initializer構文を使用すると、direct-list-initializationも実行できます。

_class any {
public:
    obj s{ "value" };
    any() {}
};
_

他に違いはありますか?

言及する価値がある他のいくつかの違いは次のとおりです。

  1. Brace-or-equal-initializerは、クラス宣言とともにヘッダーファイルに存在する必要があります。
  2. 両方を組み合わせた場合、member-initializer-listbrace-or-equal-initializerより優先されます(つまり、brace-or-equal-initializerは無視されます)。
  3. (C++ 11のみ、C++ 14まで)brace-or-equal-initializerを使用するクラスは、集約型の制約に違反します。
  4. brace-or-equal-initializer構文では、-direct-list-initialization以外のdirect-initializationを実行することはできません。
38
Piotr Skotnicki

両方の例は同等です。
タイプがコピー可能または移動可能(自分でチェック)で、NRVOが実際に実行されている場合のみ(ただし、中途半端なコンパイラーであれば当然実行されます)。

多くのコンストラクターがあり、コンストラクターチェーンが不適切である場合でも、最初の方法では自分自身を繰り返すことはできません。

また、そのメソッドを使用して、C++ 14以降の(一部の)メンバーの集約初期化とは異なるデフォルトで集約を定義できます。

7
Deduplicator

それらは同じです。

どちらもパフォーマンスの点で他より優れているわけではなく、それらを初期化する他の方法はありません。

クラス内の初期化(例の最初)の利点は、初期化の順序が暗黙的であることです。初期化リストでは、順序を明示的に指定する必要があります。順序が正しくないと、コンパイラは順序が正しくない初期化を警告します。

標準から:

12.6.2.5
nonstatic data members shall be initialized in the order they were declared 
in the class definition

リストの順序が間違っていると、GCCから次のような苦情が寄せられます。

main.cpp: In constructor 'C::C()':
main.cpp:51:9: warning: 'C::b' will be initialized after
main.cpp:51:6: warning:   'int C::a'

初期化リストの利点はおそらく好みの問題です。リストは明示的であり、通常はソースファイル内にあります。クラス内は(ほぼ間違いなく)暗黙的であり、通常はヘッダーファイルにあります。

3
Steve Lorimer