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

时间:2013-09-11 02:26:45

标签: c++ c c++11 initialization c99

考虑:

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

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

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

标准委员会排除对这种便利功能的支持的理由是什么?

5 个答案:

答案 0 :(得分:71)

7月15日&n = 17 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实际上有support for Designated Intializers,并且有许多建议要添加对标准的支持,例如:n4172和{{3} }。这些提案引用了{C ++,gcc和Clang中Daryle Walker's Proposal to Add Designation to Initializers的指定初始化器的实现声明:

  

我们相信实施的变化相对简单

但标准委员会反复,说明:

  电子工作组发现了提出的方法存在各种问题,并没有认为尝试解决问题是可行的,因为它已多次尝试并且每次都失败了

rejects such proposals帮助我看到了这种方法难以克服的问题;给出:

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

Ben Voigt's comments struct X foo = {.a = (char)f(), .b = g(), .c = h()}中调用这些函数的顺序是什么?令人惊讶的是,

  

任何初始化程序中子表达式的评估顺序是不确定的 []

(Visual C ++,1和Clang似乎有一个商定的行为,因为他们都将按此顺序进行调用:)

  1. h()
  2. f()
  3. g()
  4. 但标准的不确定性意味着如果这些函数有任何交互,结果程序状态也将是不确定的,并且编译器不会发出警告你gcc

    Is there a Way to Get Warned about Misbehaving Designated Initializers? 确实具有严格的初始化列表要求11.6.4 [dcl.init.list] 4:

      

    在braced-init-list的initializer-list中,initializer-clause(包括pack扩展(17.5.3)产生的任何结果)按照它们出现的顺序进行评估。也就是说,与给定的initializer子句相关联的每个值计算和副作用都会在每个值计算和副作用之前与在initizer列表的逗号分隔列表中跟随它的任何initializer子句相关联。

    所以支持要求按顺序执行此操作:

    1. f()
    2. g()
    3. h()
    4. 破坏与先前实施的兼容性。
      如上所述,中接受的指定初始化程序的限制已经避免了这个问题。它们提供标准化行为,保证指定初始化程序的执行顺序。

答案 1 :(得分:28)

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

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


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

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

答案 2 :(得分:22)

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

答案 3 :(得分:16)

A bit of hackery, so just sharing for fun.

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

And use it like:

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

which expands to:

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

答案 4 :(得分:5)

Two Core C99 Features 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实际上不是编译时常量值。