为什么C++ 11不支持指定的初始化列表作为C99?

xml*_*lmx 110 c c++ initialization c99 c++11

考虑:

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

int main()
{
    Person p { .age = 18 };
}
Run Code Online (Sandbox Code Playgroud)

上面的代码在C99中是合法的,但在C++ 11中不合法.

什么是标准委员会排除了这样一个方便的功能支持的理由?

Jon*_*Mee 78

7月15日'17 P0329R4被接纳进入标准:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
这带来了有限的支持 " s指定的初始化器.该限制由C.1.7 [diff.decl] .4描述如下:

struct A { int x, y; };
struct B { struct A a; };
Run Code Online (Sandbox Code Playgroud)

以下指定的初始化(在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实际上支持指定的Iniantizers,并且已经提出了许多增加对标准的支持的建议,例如:n4172Daryle Walker为初始化器添加指定的提议.这些提议引用了的Visual C++,gcc和Clang中的指定初始化器的实现声明:

我们相信这些变化实施起来相对简单

但标准委员会一再拒绝此类提案,并指出:

EWG发现了提出的方法存在各种问题,并且认为尝试解决问题并不可行,因为它已经多次尝试并且每次都失败了

Ben Voigt的评论帮助我看到了这种方法难以克服的问题; 给定:

struct X {
    int c;
    char a;
    float b;
};
Run Code Online (Sandbox Code Playgroud)

什么样的顺序将这些功能堪称: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中,initializer-clauses(包括pack扩展(17.5.3)产生的任何结果)按照它们出现的顺序进行评估.也就是说,与给定初始化子句相关联的每个值计算和副作用在每个值计算和与初始化列表的逗号分隔列表中跟随它之后的任何初始化子句相关联的副作用之前被排序.

所以支持需要按顺序执行:

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

打破与以前的实现的兼容性.
如上所述,这个问题已经被接受的指定初始化器的限制所规避.它们提供标准化行为,保证指定初始化程序的执行顺序.

  • 当然,在这段代码中:`struct X {int c; char a; 浮b; }; X x = {.a = f(),. b = g(),. c = h()};`对`h()`的调用是在`f()`或`g()之前执行的` .如果`struct X`的定义不在附近,这将是非常令人惊讶的.请记住,初始化表达式不必是无副作用的. (3认同)
  • 当然,这并不是什么新鲜事,ctor成员初始化已经有了这个问题,但它在一个类成员的定义中,所以紧耦合并不奇怪.并且指定的初始化程序不能像ctor成员初始化程序那样引用其他成员. (2认同)
  • @MattMcNabb:不,这不是更极端.但是人们希望实现类构造函数的开发人员知道成员声明顺序.而该类的消费者可能完全是一个不同的程序员.由于重点是允许初始化而不必查看成员的顺序,这似乎是提案中的致命缺陷.由于指定的初始值设定项不能引用正在构造的对象,因此第一印象是可以首先按指定顺序评估初始化表达式,然后按声明顺序初始化成员初始化.但... (2认同)
  • @JonathanMee:好了,其他问题回答了... C99总初始化是无序的,所以没有预期的要责令指定初始化.C++支撑-INIT-列表进行排序,并为指定的初始建议使用一个潜在的,令人惊讶的订单(您不能同时用词汇顺序,用于所有的支撑,初始化列表,成员顺序,用于构造函数初始化程序一致 - 列出) (2认同)
  • Jonathan:"c ++支持将要求按顺序执行此操作[...]破坏与先前c99实现的兼容性." 抱歉,我不懂这个.1.如果订单在C99中是不确定的,那么显然*任何*实际订单应该没问题,包括任何C++选择.b)不支持des.所有类型的初始化程序已经破坏了C99兼容性...... (2认同)
  • @Sz.我在C++ 17中有更多的梦想,这个功能包括......但是我很感激语言仍然在增长我总是抱怨整个.在C++ 11之前只有很多时间,我喜欢的语言似乎正在消亡.在这一点上......是的,我很感激. (2认同)

bam*_*s53 31

C++有构造函数.如果只初始化一个成员是有意义的,那么可以通过实现适当的构造函数在程序中表达.这是C++推广的一种抽象.

另一方面,指定的初始化器功能更多地是关于公开并使成员易于直接在客户端代码中访问.这会导致像18岁(年?)但身高和体重为零的人.


换句话说,指定的初始化器支持一种内部暴露的编程风格,并且客户端可以灵活地决定他们想要如何使用该类型.

C++更感兴趣的是将灵活性放在类型设计器的一边,因此设计人员可以很容易地正确使用类型并且难以正确使用.让设计者控制如何初始化类型是其中的一部分:设计者确定构造函数,类内初始化器等.

  • 是不是没有为"Person"提供构造函数的原因,其作者想要为用户设置和初始化成员提供最大的灵活性?用户也可以编写`Person p = {0,0,18};`(并且有充分的理由). (19认同)
  • 请显示一个参考链接,说明C++没有指定初始值设定项的原因.我记不起曾经看过它的建议. (12认同)
  • 最近,在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.html中,C++ 14规范中已经接受了类似的东西. (7认同)
  • @ JohannesSchaub-litb我不是在谈论纯粹机械的,近似的原因(即,它没有提交给委员会).我正在描述我认为是主导因素. - `Person`有一个非常C的设计,所以C功能可能有意义.但是,C++可能会实现更好的设计,同时也不需要指定的初始化程序. - 在我看来,删除聚合的类内初始化程序的限制更符合C++的精神,而不是指定的初始化程序. (4认同)
  • 用于替换它的C++可以命名为函数参数.但截至目前,名称论据并未正式存在.有关此提案,请参见[N4172命名参数](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm).它会使代码更容易出错,更容易阅读. (4认同)

kee*_*bus 26

有点hackery,所以只是分享乐趣.

#define with(T, ...)\
    ([&]{ T ${}; __VA_ARGS__; return $; }())
Run Code Online (Sandbox Code Playgroud)

并使用它像:

MyFunction(with(Params,
    $.Name = "Foo Bar",
    $.Age  = 18
));
Run Code Online (Sandbox Code Playgroud)

扩展到:

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

  • 我非常喜欢这个答案。 (3认同)
  • 在优化的构建中,您看不到 lambda 或其调用的痕迹。都是内联的。 (2认同)
  • 哇。甚至都不知道$是一个有效的名称。 (2认同)
  • 它受到旧版 C 编译器的支持,并保留支持以实现向后兼容性。 (2认同)

Ser*_*eyA 22

指定初始化程序目前包含在C++ 20工作主体中:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf所以我们可能最终看到它们!

  • 但请注意它们是受限制的:_在C++中,与C中的相应功能相比,指定的初始化支持受到限制.在C++中,非静态数据成员的指示符必须按声明顺序指定,数组元素的指示符和嵌套指示符不是支持,指定和非指定的初始值设定项不能在同一个初始化列表中混合.这意味着特别是,您仍然无法[轻松创建一个枚举键查找表](http:// eli .thegreenplace.net/2011/02/15 /阵列初始化与 - 枚举指数式-C的但不-C). (3认同)

wcy*_*wcy 5

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();
}
Run Code Online (Sandbox Code Playgroud)

我们在编译时知道,它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"
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

这是优化汇编代码的输出.

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   
Run Code Online (Sandbox Code Playgroud)

我们可以看到它a_point实际上不是编译时常量值.

  • 现在请尝试`constexpr point(int _x,int _y):x(_x),y(_y){}`.clang ++的优化器似乎也消除了代码中的比较.所以,这只是一个QoI问题. (8认同)
  • @musiphil至少在C++ 14中,这些C风格的结构可以通过使用赋值在constexpr函数中作为局部变量正确设置,然后从该函数返回.另外,我的观点是不要在C++中显示构造函数的替代实现,它允许优化,但是展示如果初始化的形式不同,编译器可以执行此优化.如果编译器"足够好"(即支持这种形式的优化),那么无论是使用ctor还是使用指定的初始值设定器,它都应该是无关紧要的. (2认同)