56

素朴で楽観的で、ああ.. C++ 11の統一された初期化構文の間違った見方

C++11 のユーザー定義型オブジェクトは{...}、古い構文ではなく新しい構文で構築する必要があるため、コンストラクターのオーバーロードと同様のパラメーター(...)を除きます (例: size ctor と 1 elem init_list ctor の比較)。std::initializer_liststd::vector

利点は次のとおりです。狭い暗黙の変換がなく、最も厄介な解析である一貫性 (?) に問題がありません。同じだと思っていたので問題はありませんでした(例を除いて)。

しかし、そうではありません。

純粋な狂気の物語

{}、既定のコンストラクターを呼び出します。

... 次の場合を除きます。

  • デフォルトのコンストラクターが削除され、
  • 他のコンストラクターは定義されていません。

では、むしろオブジェクトを値初期化するように見えますか?...オブジェクトがデフォルトのコンストラクターを削除した場合でも、オブジェクト{}を作成できます。これは、削除されたコンストラクターの目的全体に勝っていませんか?

...次の場合を除く:

  • オブジェクトには削除されたデフォルト コンストラクタがあり、
  • 他のコンストラクターが定義されています。

その後、 で失敗しcall to deleted constructorます。

...次の場合を除く:

  • オブジェクトには削除されたコンストラクターがあり、
  • 他のコンストラクターが定義されておらず、
  • 少なくとも非静的データ メンバー。

その後、フィールド初期化子が欠落して失敗します。

{value}ただし、オブジェクトを構築するために使用できます。

これは最初の例外と同じかもしれません (値はオブジェクトに初期化されます)

...次の場合を除く:

  • クラスには削除されたコンストラクターがあります
  • クラス内のデフォルトで初期化された少なくとも 1 つのデータ メンバー。

次に、オブジェクトを作成すること{}もできません。{value}

私はいくつかを逃したと確信しています。皮肉なことに、それは統一初期化構文と呼ばれています。繰り返しますが、UNIFORM初期化構文です。

この狂気は何ですか?

シナリオA

削除されたデフォルト コンストラクタ:

struct foo {
  foo() = delete;
};

// All bellow OK (no errors, no warnings)
foo f = foo{};
foo f = {};
foo f{}; // will use only this from now on.

シナリオB

デフォルトのコンストラクターを削除し、他のコンストラクターを削除しました

struct foo {
  foo() = delete;
  foo(int) = delete;
};

foo f{}; // OK

シナリオC

デフォルトのコンストラクターを削除し、他のコンストラクターを定義

struct foo {
  foo() = delete;
  foo(int) {};
};

foo f{}; // error call to deleted constructor

シナリオD

既定のコンストラクターが削除され、他のコンストラクターは定義されておらず、データ メンバー

struct foo {
  int a;
  foo() = delete;
};

foo f{}; // error use of deleted function foo::foo()
foo f{3}; // OK

シナリオ E

デフォルト コンストラクターの削除、T コンストラクターの削除、T データ メンバーの削除

struct foo {
  int a;
  foo() = delete;
  foo(int) = delete;
};

foo f{}; // ERROR: missing initializer
foo f{3}; // OK

シナリオ F

デフォルト コンストラクター、クラス内データ メンバー初期化子を削除

struct foo {
  int a = 3;
  foo() = delete;
};

/* Fa */ foo f{}; // ERROR: use of deleted function `foo::foo()`
/* Fb */ foo f{3}; // ERROR: no matching function to call `foo::foo(init list)`

4

3 に答える 3

37

このように物事を見ると、オブジェクトが初期化される方法には完全で完全な混乱があると簡単に言えます。

大きな違いは、foo: が集約型かどうかの型にあります。

次の場合は集計です。

  • ユーザー提供のコンストラクターはありません (削除された関数またはデフォルトの関数はユーザー提供としてカウントされません)。
  • プライベートまたは保護された非静的データ メンバーなし、
  • 非静的データ メンバーのブレースまたはイコール初期化子はありません (c++11 から (元に戻された) c++14 まで)
  • 基本クラスなし、
  • 仮想メンバー関数はありません。

そう:

  • シナリオでは ABDE:foo集合体です
  • シナリオ C:fooは集合体ではありません
  • シナリオ F:
    • c++11 では集約ではありません。
    • c++14 では集計です。
    • g++ はこれを実装しておらず、C++14 でも非集計として扱います。
      • 4.9これを実装しません。
      • 5.2.0する
      • 5.2.1 ubuntuしません(おそらく回帰)

タイプ T のオブジェクトのリスト初期化の効果は次のとおりです。

  • ...
  • T が集約型の場合、集約の初期化が実行されます。これにより、シナリオ ABDE (および C++14 の F) が処理されます。
  • それ以外の場合、 T のコンストラクターは 2 つのフェーズで考慮されます。
    • std::initializer_list を取るすべてのコンストラクター ...
    • それ以外の場合 [...] T のすべてのコンストラクターがオーバーロードの解決に参加します [...] これにより、C (および C++11 の F) が処理されます。
  • ...

:

タイプ T のオブジェクトの集約初期化 (シナリオ ABDE (F c++14)):

  • 各非静的クラス メンバーは、クラス定義に出現する順序で、初期化子リストの対応する句からコピー初期化されます。(配列参照省略)

TL;DR

これらのルールはすべて非常に複雑に見え、頭痛の種になることがあります。私は個人的にこれを単純化しすぎています(それによって自分の足を撃ったとしても、そうです:数十日間の頭痛ではなく、2日間病院で過ごすと思います):

  • 集計の場合、各データ メンバーはリスト初期化子の要素から初期化されます。
  • そうでなければコンストラクタを呼び出す

これは、削除されたコンストラクターの目的全体に勝っていませんか?

まあ、それについてはわかりませんが、解決策はfoo集合体ではないことです。オーバーヘッドを追加せず、オブジェクトの使用構文を変更しない最も一般的な形式は、空の構造体から継承させることです。

struct dummy_t {};

struct foo : dummy_t {
  foo() = delete;
};

foo f{}; // ERROR call to deleted constructor

状況によっては (非静的メンバーがまったくないと思います)、代わりにデストラクタを削除することもできます (これにより、オブジェクトはどのコンテキストでもインスタンス化できなくなります)。

struct foo {
  ~foo() = delete;
};

foo f{}; // ERROR use of deleted function `foo::~foo()`

この回答は、次から収集した情報を使用します。

この投稿の修正と改善に協力してくれた@MMに感謝します。

于 2015-11-29T21:22:00.753 に答える