考虑:
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的
有限支持s 指定初始化器。这个限制在 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 的 Proposal to Add Designation to Initializers。提案引用了c99在 Visual C++、gcc 和 Clang 中的指定初始化程序的实现,声称:
我们相信这些变化将相对简单地实施
但标准委员会一再拒绝此类提议,称:
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 warned about Misbehave Designated Initializers?
c++ 确实有严格的初始化列表要求 11.6.4[dcl.init.list]4:
在花括号初始化列表的初始化列表中,初始化子句,包括任何由包扩展 (17.5.3) 产生的子句,按照它们出现的顺序进行评估。也就是说,与给定初始化子句关联的每个值计算和副作用都在初始化器列表的逗号分隔列表中与跟随它的任何初始化子句关联的每个值计算和副作用之前进行排序。
因此,c++支持需要按以下顺序执行:
f()
g()
h()
破坏与以前的c99实现的兼容性。
如上所述,这个问题已经被c++20接受的指定初始化器的限制所规避。它们提供了标准化的行为,保证了指定初始化器的执行顺序。
C++ 有构造函数。如果只初始化一个成员是有意义的,那么可以通过实现适当的构造函数在程序中表达。这是 C++ 提倡的那种抽象。
另一方面,指定的初始值设定项功能更多的是公开和使成员易于直接在客户端代码中访问。这会导致一个人 18 岁(岁?)但身高和体重为零。
换句话说,指定的初始化器支持暴露内部结构的编程风格,并且客户端可以灵活地决定他们希望如何使用该类型。
C++ 更感兴趣的是把灵活性放在类型设计者的一边,因此设计者可以使正确使用类型变得容易,并且难以错误使用。让设计人员控制如何初始化类型是其中的一部分:设计人员确定构造函数、类内初始化程序等。
有点hackery,所以只是为了好玩而分享。
#define with(T, ...)\
([&]{ T ${}; __VA_ARGS__; return $; }())
并像这样使用它:
MyFunction(with(Params,
$.Name = "Foo Bar",
$.Age = 18
));
扩展为:
MyFunction(([&] {
Params ${};
$.Name = "Foo Bar", $.Age = 18;
return $;
}()));
指定的初始化程序当前包含在 C++20 的工作主体中:http ://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf所以我们可能最终会看到它们!
C++11 缺少的两个 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
并不是一个真正的编译时间常数值。