考慮してください:
struct Person
{
int height;
int weight;
int age;
};
int main()
{
Person p { .age = 18 };
}
上記のコードはC99では有効ですが、C++ 11では無効です。
そのような便利な機能のサポートを除外するための c ++ 11 標準委員会の根拠は何でしたか?
C++にはコンストラクターがあります。 1つのメンバーのみを初期化することが理にかなっている場合は、適切なコンストラクターを実装することでプログラムで表現できます。これは、C++が促進する一種の抽象化です。
一方、指定されたイニシャライザー機能は、クライアントコードでメンバーを直接アクセスしやすくすることと公開することに関するものです。これは、18歳(年?)であるが、身長と体重がゼロの人がいるようなものにつながります。
言い換えれば、指定されたイニシャライザは内部が公開されるプログラミングスタイルをサポートし、クライアントはタイプをどのように使用するかを決定する柔軟性を与えられます。
C++は、代わりに型のdesignerの柔軟性を高めることに関心があるため、設計者はそれを簡単にすることができます。型を正しく使用し、誤って使用するのは困難です。型を初期化する方法をデザイナーに制御させることは、この一部です。デザイナーは、コンストラクター、クラス内の初期化子などを決定します。
17年7月15日 P0329R4 は c ++ 2 標準に受け入れられました: http://www.open-std.org/jtc1/sc22/ wg21/docs/papers/2017/p0329r4.pdf
これにより、 c99 の指定初期化子のサポートが制限されます。この制限は、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++では無効です。これは、すべてのデータメンバーが指定子によって初期化される必要があるためです。c ++ 17 およびそれ以前のBoostには Designated Intializersのサポート があり、 c ++ 標準にサポートを追加するための多くの提案がありました。例: n4172 および 初期化子に指定を追加するためのDaryle Walkerの提案 。提案では、Visual C++、gcc、およびClangでの c99 のDesignated Initializerの実装を引用しています。
変更は比較的簡単に実装できると考えています
しかし、標準委員会は繰り返し そのような提案を拒否します 、と述べています:
EWGは提案されたアプローチでさまざまな問題を発見しましたが、何度も試行され、失敗するたびに問題を解決することは現実的ではないと考えました
Ben Voigtのコメント は、このアプローチで克服できない問題を理解するのに役立ちました。与えられた:
struct X {
int c;
char a;
float b;
};
c99 :struct X foo = {.a = (char)f(), .b = g(), .c = h()}
でこれらの関数はどの順序で呼び出されますか?驚いたことに、 c99 :
初期化子の部分式の評価の順序は不定に順序付けされます [ 1 ]
(Visual C++、 gcc 、およびClangはすべて次の順序で呼び出しを行うため、動作に同意しているようです:)
h()
f()
g()
しかし、標準の不確定な性質は、これらの関数に何らかの相互作用がある場合、結果のプログラム状態も不確定になることを意味します。コンパイラーは警告しません : 指定された初期化子の不正な動作について警告する方法はありますか?
c ++doesには、初期化子リストの厳しい要件があります11.6.4 [dcl.init.list] 4:
Braced-init-listのinitializer-list内では、pack拡張(17.5.3)の結果を含むinitializer-clausesが、出現順に評価されます。つまり、指定されたinitializer-clauseに関連付けられたすべての値の計算と副作用は、initializer-listのコンマ区切りリストでそれに続くinitializer-clauseに関連付けられたすべての値の計算と副作用の前にシーケンスされます。
c ++ サポートでは、これを次の順序で実行する必要があります。
f()
g()
h()
以前の c99 実装との互換性を壊します。
上で説明したように、この問題は c ++ 2 で受け入れられる指定イニシャライザーの制限により回避されました。これらは、指定された初期化子の実行順序を保証する標準化された動作を提供します。
現在、指定された初期化子はC++ 20の作業本文に含まれています。 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf 最終的にそれらを見るかもしれません!
ちょっとしたハッカーです。楽しみのために共有するだけです。
#define with(T, ...)\
([&]{ T ${}; __VA_ARGS__; return $; }())
そして次のように使用します:
MyFunction(with(Params,
$.Name = "Foo Bar",
$.Age = 18
));
次のように展開されます。
MyFunction(([&] {
Params ${};
$.Name = "Foo Bar", $.Age = 18;
return $;
}()));
2つのコアC99機能 C++ 11 Lacksは「指定された初期化子と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
は実際にはコンパイル時の定数値ではないことがわかります。