検討:
struct Person
{
int height;
int weight;
int age;
};
int main()
{
Person p { .age = 18 };
}
上記のコードは C99 では有効ですが、C++11 では無効です。
このような便利な機能のサポートを除外するc++11標準委員会の根拠は何ですか?
検討:
struct Person
{
int height;
int weight;
int age;
};
int main()
{
Person p { .age = 18 };
}
上記のコードは C99 では有効ですが、C++11 では無効です。
このような便利な機能のサポートを除外するc++11標準委員会の根拠は何ですか?
'17 年 7 月 15 日、 P0329R4がc++20標準に受け入れられました: 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++標準にサポートを追加するための多数の提案がありました。この提案は、Visual C++、gcc、および Clang でのc99の Designated Initializers の実装を引用しており、次のように主張しています。
変更は比較的簡単に実装できると考えています
しかし、標準委員会はそのような提案を繰り返し拒否し、次のように述べています。
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()
しかし、標準の不確定な性質は、これらの関数に何らかの相互作用があった場合、結果として生じるプログラムの状態も不確定になり、コンパイラは警告を出さないことを意味します: Is there a Way to Get Warning about Misbehaving Designated Initializers?
c++ には厳格なイニシャライザ リスト要件があります 11.6.4[dcl.init.list]4:
ブレース初期化リストの初期化リスト内では、パック展開 (17.5.3) の結果を含む初期化節は、出現順に評価されます。つまり、指定された initializer-clause に関連付けられたすべての値の計算と副作用は、initializer-list のコンマ区切りのリストでそれに続くすべての initializer-clause に関連付けられたすべての値の計算と副作用の前に並べられます。
したがって、C++サポートでは、これを次の順序で実行する必要がありました。
f()
g()
h()
以前のc99実装との互換性を壊しています。
上記で説明したように、この問題は、c++20に受け入れられる Designated Initializers の制限によって回避されています。標準化された動作を提供し、Designated Initializer の実行順序を保証します。
C++ にはコンストラクターがあります。メンバーを 1 つだけ初期化することが理にかなっている場合は、適切なコンストラクターを実装することによってプログラムで表現できます。これは、C++ が促進する一種の抽象化です。
一方、指定された初期化子機能は、メンバーを公開して、クライアント コードで直接アクセスしやすくすることを目的としています。これは、18 歳 (歳?) でも身長と体重がゼロの人がいるようなものです。
つまり、指定されたイニシャライザは、内部が公開されるプログラミング スタイルをサポートし、クライアントは型をどのように使用するかを柔軟に決定できます。
C++ は、代わりに型の設計者側に柔軟性を持たせることに関心があるため、設計者は型を正しく使用しやすくし、誤って使用しにくくすることができます。型を初期化する方法を設計者が制御できるようにすることは、この一部です。設計者は、コンストラクター、クラス内初期化子などを決定します。
指定されたイニシャライザは現在、C++20 の作業本体に含まれています: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdfので、最終的に表示される可能性があります!
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
これは実際にはコンパイル時の定数値ではないことがわかります。