C ++ 11がC99として指定された初期化リストをサポートしないのはなぜですか?[閉まっている]


121

考慮してください:

struct Person
{
    int height;
    int weight;
    int age;
};

int main()
{
    Person p { .age = 18 };
}

上記のコードはC99では合法ですが、C ++ 11では合法ではありません。

何でしたか そのような便利な機能のサポートを除外するための標準委員会の理論的根拠?


10
設計委員会がこれを含めることは明らかに意味がなかった、または単に会議で取り上げられなかっただけのようです。C99で指定された初期化子がC ++仕様バージョンのいずれにもないことは注目に値します。コンストラクターは推奨される初期化構成体のようであり、正当な理由により、正しく記述した場合、一貫したオブジェクトの初期化が保証されます。
Robert Harvey

19
あなたの推論は逆です。言語には、機能がないという根拠は必要ありません。機能があり、強力なものがあるという根拠が必要です。現状では、C ++は十分に肥大化しています。
Matthieu M.

42
十分な理由(これは、愚かなラッパーを書くこと以外はコンストラクターで解決できない)は、C ++を使用するかどうかに関係なく、ほとんどの実際のAPIはC ++ではなくCであり、それらのいくつかは設定したい構造を提供することになるためです1つまたは2つのフィールド(最初のフィールドである必要はありません)。ただし、残りをゼロで初期化する必要があります。Win32 API OVERLAPPEDはそのような例です。記述できることにより={.Offset=12345};、コードがより明確になります(おそらくエラーが発生しにくくなります)。BSDソケットも同様の例です。
デイモン

14
のコードmainは正当なC99 ではありません。読む必要があります struct Person p = { .age = 18 };
chqrlie

14
FYI C ++ 20は指定されたイニシャライザをサポートします
Andrew Tomazos 2018年

回答:


34

C ++にはコンストラクタがあります。1つのメンバーのみを初期化することに意味がある場合は、適切なコンストラクターを実装することでプログラムで表現できます。これは、C ++が促進する一種の抽象化です。

一方、指定された初期化機能は、メンバーを公開してクライアントコードで直接簡単にアクセスできるようにすることを目的としています。これにより、18歳(年?)の人の身長と体重が0のような人がいます。


言い換えると、指定された初期化子は内部が公開されるプログラミングスタイルをサポートし、クライアントは型をどのように使用するかを決定する柔軟性を与えられます。

C ++は、代わりに型のデザイナ側に柔軟性を持たせることにより関心があるので、デザイナは型を正しく使用しやすく、誤って使用することを困難にすることができます。型を初期化する方法をデザイナーが制御することは、これの一部です。デザイナーは、コンストラクター、クラス内初期化子などを決定します。


12
C ++が指定された初期化子を持たない理由であるとあなたが言うことの参照リンクを示してください。その提案を見た覚えがありません。
Johannes Schaub-litb 2013

20
Person作成者がメンバーを設定および初期化するための可能な限りの柔軟性を提供したかったため、コンストラクターを提供しない理由そのものではありませんか?また、ユーザーは既にPerson p = { 0, 0, 18 };(そして正当な理由で)書き込むこともできます。
Johannes Schaub-litb 2013

7
最近、似たようなものがopen-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.htmlによってC ++ 14仕様に受け入れられました。
Johannes Schaub-litb 2013

4
@ JohannesSchaub-litb私は純粋に機械的な、最も近い原因について話しているのではありません(つまり、委員会に提案されていません)。私が支配的な要因であると私が信じるものを説明しています。— Person非常にCの設計になっているため、Cの機能が意味をなす場合があります。ただし、C ++はおそらく、より優れた設計を可能にし、指定された初期化子の必要性も取り除きます。—私の見解では、集約のクラス内初期化子に対する制限を取り除くことは、指定された初期化子よりもC ++の精神に沿ったものです。
bames53 2013

4
これに対するC ++の置き換えは、名前付き関数の引数である場合があります。しかし、現時点では、名前の引数は正式には存在しません。この提案については、N4172名前付き引数を参照してください。これにより、エラーが発生しにくくなり、コードが読みやすくなります。
David Baird

89

2017年7月15日、P0329R4標準:http : //www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
これにより、サポートが制限されますの指定イニシャライザ。この制限は、C.1.7 [diff.decl] .4で次のように説明されています。

struct A { int x, y; };
struct B { struct A a; };

Cで有効な次の指定初期化は、C ++では制限されています。

  • struct A a = { .y = 1, .x = 2 } C ++では無効です。指定子はデータメンバーの宣言順に現れる必要があるためです。
  • int arr[3] = { [1] = 5 } 配列指定の初期化がサポートされていないため、C ++では無効です
  • struct B b = {.a.x = 0} C ++では指定子をネストできないため無効です
  • struct A c = {.x = 1, 2} すべてのデータメンバーまたはデータメンバーのいずれも指定子によって初期化する必要がないため、C ++では無効です。

ために 以前のBoost は指定インティライザーを実際にサポートしており、サポートを追加する多くの提案がありました。標準、たとえば:n4172およびDaryle Walkerの提案による指定を初期化子に追加。提案は、の指定したVisual C ++、gcc、およびClangの指定イニシャライザ:

変更は比較的簡単に実装できると考えています

しかし、標準委員会はそのような提案を繰り返し拒否し、次のように述べている。

EWGは提案されたアプローチでさまざまな問題を発見し、何度も試行され、失敗するたびに問題を解決しようとすることは現実的ではないと考えました

Ben Voigtのコメントは、このアプローチで解決できない問題を見つけるのに役立ちました。与えられた:

struct X {
    int c;
    char a;
    float b;
};

これらの関数が呼び出される順序 struct X foo = {.a = (char)f(), .b = g(), .c = h()}?意外にも、

イニシャライザ内の部分式の評価順序は、不確定に順序付けられます[ 1 ]

(Visual C ++、gcc、およびClangはすべてこの順序で呼び出しを行うため、動作が合意されているようです:)

  1. h()
  2. f()
  3. g()

しかし、標準の不確定な性質は、これらの関数に何らかの相互作用があった場合、結果のプログラム状態も不確定であり、コンパイラーが警告しないということを意味します指定された初期化子の誤動作について警告する方法はありますか?

初期化リストの厳しい要件ありません11.6.4 [dcl.init.list] 4:

braced-init-listのinitializer-list内では、pack展開(17.5.3)の結果を含むinitializer-clauseが、出現順に評価されます。つまり、特定のinitializer-clauseに関連付けられているすべての値の計算と副作用は、initializer-listのコンマ区切りリスト内のそれに続くすべてのinitializer-clauseに関連付けられているすべての値の計算と副作用の前にシーケンスされます。

そう サポートでは、次の順序で実行する必要があります。

  1. f()
  2. g()
  3. h()

以前との互換性を破る 実装。
上記で説明したように、この問題は、指定イニシャライザの制限により回避されました。。それらは標準化された動作を提供し、指定イニシャライザの実行順序を保証します。


3
確かに、このコードでは:またはの前にstruct X { int c; char a; float b; }; X x = { .a = f(), .b = g(), .c = h() };への呼び出しh()が実行されます。の定義が近くにない場合、これは非常に驚くべきことになります。初期化式は副作用のないものである必要はありません。f()g()struct X
Ben Voigt 2015年

2
もちろん、これは新しいことではありません。ctorメンバーの初期化にはすでにこの問題がありますが、クラスメンバーの定義にあるため、密結合は当然です。また、指定された初期化子は、ctorメンバー初期化子ができるように他のメンバーを参照できません。
Ben Voigt 2015年

2
@MattMcNabb:いいえ、それほど極端ではありません。ただし、クラスコンストラクターを実装する開発者は、メンバー宣言の順序を知っている必要があります。一方、クラスのコンシューマはまったく異なるプログラマである可能性があります。重要なのは、メンバーの順序を調べなくても初期化できるようにすることなので、これは提案の致命的な欠陥のようです。指定された初期化子は構築中のオブジェクトを参照できないため、最初の印象は、初期化式が最初に指定順に評価され、次にメンバーの初期化が宣言順に評価されるというものです。しかし...
Ben Voigt 2015年

2
@JonathanMee:さて、他の質問は答えました... C99集約初期化子は順序付けされていないため、指定された初期化子が順序付けされることは期待されていません。C ++ braced-init-listsは順序付けられており、指定された初期化子の提案では、驚くほどの順序が使用される可能性があります(すべてのbraced-initリストに使用される字句順序と、ctor-initializerに使用されるメンバー順序の両方と一貫性を保つことはできません) -lists)
Ben Voigt

3
ジョナサン:「c ++のサポートでは、これを[...]以前のc99実装との互換性を壊す順序で実行する必要があったでしょう。」すみません、これはわかりません。1. C99で順序が不確定の場合、任意のC ++の選択を含め、実際の順序は明らかに問題ありません。b)desをサポートしていません。イニシャライザは、なんとなくC99の互換性をさらに壊しています...
Sz。

34

少しハッカーなので、楽しみのために共有するだけです。

#define with(T, ...)\
    ([&]{ T ${}; __VA_ARGS__; return $; }())

そしてそれを次のように使用します:

MyFunction(with(Params,
    $.Name = "Foo Bar",
    $.Age  = 18
));

これは次のように展開されます。

MyFunction(([&] {
 Params ${};
 $.Name = "Foo Bar", $.Age = 18;
 return $;
}()));

きちんと、$type という名前の変数でラムダを作成し、Tそれを返す前にメンバーを直接割り当てます。気の利いた。パフォーマンスの問題はあるのでしょうか。
TankorSmash

1
最適化されたビルドでは、ラムダの痕跡やその呼び出しは表示されません。すべてインライン化されています。
keebus

1
私はこの答えが大好きです。
Seph Reed

6
うわぁ。$が有効な名前であることさえ知りませんでした。
クリスワッツ

これは、レガシーCコンパイラーによってサポートされ、下位互換性のために維持されました。
keebus

22

初期化子指定現在の仕事のC ++ 20体に含まれています:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf我々は最終的にそれらが表示される場合がありますので!


3
ただし、制限されていることに注意してください。C++では、指定された初期化サポートはCの対応する機能と比較して制限されています。C++では、非静的データメンバーの指定子は宣言順に指定する必要があり、配列要素の指定子とネストされた指定子は指定しませんサポートされ、指定されたイニシャライザと指定されていないイニシャライザを同じイニシャライザリストに混在させることはできません。つまり、特に、列挙型キーのルックアップテーブル簡単に作成することはできません。
ルスラン

@ルスラン:なぜC ++がそれらをそんなに制限したのかしら?項目の値が評価および/または構造体に書き込まれる順序が、初期化リストで項目が指定されている順序と一致するか、メンバーが構造体に表示される順序と一致するかについて混乱があるかもしれませんが、これに対する解決策は、初期化式が任意の順序で実行され、オブジェクトのライフタイムは初期化が完了するまで開始されないということです(&オペレーターは、ライフタイム中にオブジェクト持つアドレスを返します)。
スーパーキャット2018

5

C ++ 11に欠けている2つのコアC99機能で、「指定された初期化子とC ++」について言及しています。

可能性のある最適化に関連する「指定イニシャライザ」と思います。ここでは例として「gcc / g ++」5.1を使用します。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>    
struct point {
    int x;
    int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

コンパイル時にわかっていて、a_point.xゼロなので、それがfoo単一に最適化されることを期待できましたprintf

$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
   0x00000000004004f0 <+0>: sub    $0x8,%rsp
   0x00000000004004f4 <+4>: mov    $0x4005bc,%edi
   0x00000000004004f9 <+9>: xor    %eax,%eax
   0x00000000004004fb <+11>:    callq  0x4003a0 <printf@plt>
   0x0000000000400500 <+16>:    xor    %eax,%eax
   0x0000000000400502 <+18>:    add    $0x8,%rsp
   0x0000000000400506 <+22>:    retq   
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc:   "x == 0"

foo印刷x == 0のみに最適化されています。

C ++バージョンの場合

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
    point(int _x,int _y):x(_x),y(_y){}
    int x;
    int y;
};
const struct point a_point(0,0);
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

そして、これは最適化されたアセンブルコードの出力です。

g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>:    push   %rbx
0x00000000004005c1 <+1>:    mov    0x200489(%rip),%ebx        # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>:    test   %ebx,%ebx
0x00000000004005c9 <+9>:    je     0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>:   mov    $0x1,%ebx
0x00000000004005d0 <+16>:   mov    $0x4006a3,%edi
0x00000000004005d5 <+21>:   xor    %eax,%eax
0x00000000004005d7 <+23>:   callq  0x400460 <printf@plt>
0x00000000004005dc <+28>:   mov    %ebx,%eax
0x00000000004005de <+30>:   pop    %rbx
0x00000000004005df <+31>:   retq   
0x00000000004005e0 <+32>:   mov    $0x40069c,%edi
0x00000000004005e5 <+37>:   xor    %eax,%eax
0x00000000004005e7 <+39>:   callq  0x400460 <printf@plt>
0x00000000004005ec <+44>:   mov    %ebx,%eax
0x00000000004005ee <+46>:   pop    %rbx
0x00000000004005ef <+47>:   retq   

これa_pointは実際にはコンパイル時の定数値ではないことがわかります。


8
ぜひお試しくださいconstexpr point(int _x,int _y):x(_x),y(_y){}。clang ++のオプティマイザは、コード内の比較も排除するようです。したがって、これはQoIの問題にすぎません。
dyp

また、内部リンクがある場合は、a_pointオブジェクト全体が最適化されることを期待します。つまり、それを匿名の名前空間に配置して、何が起こるかを確認します。goo.gl/wNL0HC
Arvid

@dyp:タイプがあなたの管理下にある場合のみ、コンストラクタを定義することさえ可能です。たとえば、struct addrinfoまたはstruct sockaddr_inに対してそれを行うことはできないため、宣言とは別の割り当てが残ります。
ムシフィル2017年

2
@musiphil少なくともC ++ 14では、これらのCスタイルの構造体をconstexpr関数でローカル変数として割り当てを使用して適切に設定し、その関数から返すことができます。さらに、私のポイントは、最適化を可能にするC ++のコンストラクターの代替実装を示すことではなく、初期化の形式が異なる場合にコンパイラーがこの最適化を実行できることを示しています。コンパイラーが「十分」である(つまり、この形式の最適化をサポートしている)場合、ctorを使用するか、指定された初期化子を使用するか、または他の何かを使用するかどうかは関係ありません。
dyp '25年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.