ブレースで囲まれたイニシャライザをいつ使用するのですか?


94

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};
}

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

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

どの構文を選択すべきかという普遍的なガイドラインはあるのでしょうか。


1
{}初期化からの意図しない動作の例:string(50、 'x')vs string {50、 'x'} ここ
P i

回答:


64

私が考えて、次は適切なガイドラインが考えられます。

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

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

  • 初期化する値が保存される値ではなく、オブジェクトの意図された値/状態を表す場合は、括弧を使用します。たとえば、aのサイズ引数vectorまたはのファイル名引数ですfstream


4
@ user1304032:ロケールは文字列ではないため、コピーの初期化を使用しません。ロケールには文字列も含まれません(その文字列は実装の詳細として格納される可能性がありますが、それはその目的ではありません)。したがって、中括弧の初期化は使用しません。したがって、ガイドラインでは括弧の初期化を使用するように指示されています。
celtschk

2
私はこのガイドラインを個人的に気に入っており、一般的なコードでもうまく機能します。いくつかの例外(T {}または最も厄介な解析のような構文上の理由)がありますが、一般的にはこれは良いアドバイスだと思います。これは私の主観的な意見なので、他の回答も確認する必要があります。
helami

2
@celtschk:コピー不可、移動不可のタイプでは機能しません。type var{};します。
ildjarn

2
@celtschk:頻繁に発生することだと言っているわけではありませんが、入力が少なく、より多くのコンテキストで機能するので、欠点は何ですか?
ildjarn

2
私のガイドラインでは、コピーの初期化は絶対に必要ありません。;-]
ildjarn 2012

26

私は普遍的なガイドラインが決してないだろうと確信しています。私のアプローチは、常に覚えている中かっこを使用することです

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

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


6
中括弧には、誤ってリストコンストラクターを呼び出すことができるという欠点があります。丸かっこはありません。それがデフォルトで丸括弧を使用する理由ではありませんか?
helami

4
@user:int i = 0;私は誰もint i{0}そこでは使用しないと思いますが、混乱するかもしれません(また、0typeの場合int絞り込みはありません)。それ以外の場合は、Juanchoのアドバイスに従います。{}を優先し、そうすべきでないいくつかのケースに注意してください。コンストラクターの引数として初期化子リストを受け取る型はそれほど多くないことに注意してください。コンテナーとコンテナーに似た型(タプル...)にそれらがあると期待できますが、ほとんどのコードは適切なコンストラクターを呼び出します。
DavidRodríguez-dribeas

3
@ user1304032絞り込みを気にするかどうかによって異なります。私はそうするのでint i{some floating point}、黙って切り捨てるのではなく、コンパイラがそれをエラーであると伝えるのを好みます。
juanchopanza

3
「{}を優先し、そうすべきでないいくつかのケースに注意してください」:2つのクラスに意味的に同等のコンストラクターがあるが、1つのクラスにも初期化子リストがあるとします。2つの同等のコンストラクターを異なる方法で呼び出す必要がありますか?
helami

3
@helami:「2つのクラスに意味的に同等のコンストラクターがあるが、1つのクラスにも初期化子リストがあるとしましょう。2つの同等のコンストラクターを異なる方法で呼び出す必要がありますか?」私が最も慎重な解析に遭遇したとしましょう。これは、任意のインスタンスの任意のコンストラクターで発生する可能性があります。{}絶対にできない場合を除いて、単に「初期化」を意味するのに使用する場合は、これを回避する方がはるかに簡単です。
Nicol Bolas

16

一般的なコード(つまり、テンプレート)の外では、どこでも中括弧を使用できます(私もそうです)。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 {});がifの場合Tはサイズ2 intのベクトルを、Tがの場合はサイズ20のベクトルを作成しますstd::string。ここで非常に悪いことが起こっていることを示す非常にわかりやすい兆候は、ここであなたを救うことができる特性がないことです(たとえば、SFINAEを使用):std::is_constructible直接初期化の観点からですが、直接- 干渉するコンストラクターがない場合のみ初期化しますstd::initializer_list。同様にstd::is_convertible役に立ちません。

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

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

ただし、すべてのブレースが悪いことを意味するわけではありません。前の例の修正を考慮してください。

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

std::unique_ptr<T>実際の型はテンプレートパラメータに依存しますが、これでもを構築するために中括弧を使用しますT


私の例のいくつかは確かではなく、例えば符号なしの型を使用する必要があるかもしれません@interjay make_unique<T>(20u, T {})のためTのいずれかであることunsignedstd::string。詳細についてはあまりわかりません。(たとえば、完全初期化関数に関する直接初期化と中括弧初期化に関する期待にもコメントしていることに注意してください。)std::string c("qux");文法内のメンバー関数宣言とのあいまいさを避けるために、クラス内初期化として機能するように指定されていません。
Luc Danton、2012

@interjay最初の点ではあなたに同意しません。8.5.4リストの初期化と13.3.1.7リストの初期化による初期化を確認してください。2番目については、私が書いたもの(クラス内の初期化に関するもの)やC ++の文法(例:brace-or-equal-initializerを参照するmember-declarator)を詳しく調べる必要があります。
Luc Danton

うーん、あなたは正しい-私は以前私が言っていることを確認しているように見えたGCC 4.5でテストしていたが、GCC 4.6はあなたに同意する。そして、あなたがクラス内の初期化について話しているという事実を逃しました。謝罪いたします。
interjay
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.