web-dev-qa-db-ja.com

括弧で囲まれた初期化子を使用する場合

C++ 11には、クラスを初期化するための新しい構文があり、変数を初期化する多くの可能性を提供します。

{ // Example 1
  int b(1);
  int a{1};
  int c = 1;
  int d = {1};
}
{ // Example 2
  std::complex<double> b(3,4);
  std::complex<double> a{3,4};
  std::complex<double> c = {3,4};
  auto d = std::complex<double>(3,4);
  auto e = std::complex<double>{3,4};
}
{ // Example 3
  std::string a(3,'x');
  std::string b{3,'x'}; // oops
}
{ // Example 4
  std::function<int(int,int)> a(std::plus<int>());
  std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
  std::unique_ptr<int> a(new int(5));
  std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
  std::locale::global(std::locale("")); // copied from 22.4.8.3
  std::locale::global(std::locale{""});
}
{ // Example 7
  std::default_random_engine a {}; // Stroustrup's FAQ
  std::default_random_engine b;
}
{ // Example 8
  duration<long> a = 5; // Stroustrup's FAQ too
  duration<long> b(5);
  duration<long> c {5};
}

宣言する変数ごとに、どの初期化構文を使用する必要があるかを考える必要があり、これによりコーディング速度が低下します。中かっこを導入するつもりはなかったと思います。

テンプレートコードに関しては、構文を変更すると異なる意味につながる可能性があるため、正しい方法を実行することが不可欠です。

どの構文を選択すべきかについての普遍的なガイドラインがあるのだろうか。

90
helami

I 思考以下は良いガイドラインです。

  • 初期化する(単一の)値がオブジェクトの正確な値である場合、copy(=)初期化(エラーが発生した場合、通常、提供された値を異なる方法で解釈する明示的なコンストラクターを誤って呼び出すことはありません)。コピーの初期化が使用できない場所では、ブレースの初期化に正しいセマンティクスがあるかどうかを確認し、ある場合はそれを使用します。それ以外の場合は、括弧の初期化を使用します(それも使用できない場合は、とにかく運が悪いです)。

  • 初期化する値がオブジェクトに格納される(ベクトル/配列の要素、または複素数の実数部/虚数部など)になる値のリストである場合、中括弧の初期化を使用します可能な場合は。

  • 初期化する値がnot保存する値であるが、describeオブジェクトの目的の値/状態である場合は、括弧を使用します。例は、vectorのサイズ引数またはfstreamのファイル名引数です。

61
celtschk

普遍的なガイドラインは決してないでしょう。私のアプローチは、常に中括弧を使用することです。

  1. 初期化子リストのコンストラクターは、他のコンストラクターよりも優先されます
  2. すべての標準ライブラリコンテナとstd :: basic_stringには、初期化リストコンストラクタがあります。
  3. 中括弧の初期化では、変換を絞り込むことはできません。

そのため、丸括弧と中括弧は互換性がありません。しかし、それらの違いを知ることで、ほとんどの場合、丸括弧の初期化を使用することができます(現在、コンパイラのバグではない場合もあります)。

25
juanchopanza

汎用コード(テンプレートなど)の外で、どこでもブレースを使用できます(そして、私はそうします)。 1つの利点は、たとえばクラス内の初期化でも、どこでも機能することです。

_struct foo {
    // Ok
    std::string a = { "foo" };

    // Also ok
    std::string b { "bar" };

    // Not possible
    std::string c("qux");

    // For completeness this is possible
    std::string d = "baz";
};
_

または関数の引数の場合:

_void foo(std::pair<int, double*>);
foo({ 42, nullptr });
// Not possible with parentheses without spelling out the type:
foo(std::pair<int, double*>(42, nullptr));
_

_T t = { init };_スタイルと_T t { init };_スタイルの間にあまり注意を払っていない変数については、その違いはわずかであり、最悪の場合、explicitの誤用に関する有用なコンパイラメッセージが表示されるだけです。コンストラクタ。

_std::initializer_list_を受け入れる型の場合、明らかに_std::initializer_list_以外のコンストラクターが必要になることがあります(古典的な例はstd::vector<int> twenty_answers(20, 42);です)。その場合、ブレースを使用しないでかまいません。


ジェネリックコード(テンプレートなど)に関しては、最後の段落で警告が表示されるはずです。以下を考慮してください。

_template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }
_

auto p = make_unique<std::vector<T>>(20, T {});は、Tが例えば2である場合、サイズ2のベクトルを作成します。 int、またはTが_std::string_の場合はサイズ20のベクトル。ここで非常に悪いことが起こっているという非常に明白な兆候は、ここであなたを救うことができるno特性があるということです(例えばSFINAEで):_std::is_constructible_は直接初期化の観点から見たものですが、ブレース初期化を使用しているので、直接初期化を延期し、ifだけがコンストラクターを取りません_std::initializer_list_が干渉しています。同様に、_std::is_convertible_は役に立ちません。

実際にそれを修正できる特性を手で回すことができるかどうかを調査しましたが、私はそれについて過度に楽観的ではありません。いずれにせよ、私たちは多くを失いそうにないと思います。make_unique<T>(foo, bar)T(foo, bar)と等価な構造になるという事実は非常に直感的だと思います。特にmake_unique<T>({ foo, bar })はまったく異なるため、foobarの型が同じ場合にのみ意味があります。

したがって、汎用コードでは、値の初期化にブレースのみを使用します(たとえば_T t {};_または_T t = {};_)、これは非常に便利で、C++よりも優れていると思います03方法T t = T();それ以外の場合は、直接初期化構文(つまり、T t(a0, a1, a2);)、または場合によってはデフォルトの構成(_T t; stream >> t;_を使用する唯一のケースです)。

それは、all中括弧が悪いことを意味するわけではありません。前の例を修正して考えてみましょう:

_template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }
_

実際の型はテンプレートパラメータTに依存していますが、これでも_std::unique_ptr<T>_の構築に中括弧が使用されます。

16
Luc Danton