C ++ 11奇怪的大括号初始化行为

时间:2015-02-09 11:40:06

标签: c++ c++11

我不明白C ++ 11大括号初始化规则是如何工作的。 有了这段代码:

struct Position_pod {
    int x,y,z;
};

class Position {
public:
    Position(int x=0, int y=0, int z=0):x(x),y(y),z(z){}
    int x,y,z;
};

struct text_descriptor {
    int             id;
    Position_pod    pos;
    const int       &constNum;
};

struct text_descriptor td[3] = {
     {0, {465,223}, 123},
     {1, {465,262}, 123},
};

int main() 
{
    return 0;
}

请注意,该数组声明有3个元素,但只提供了2个初始值设定项。

然而,它编译没有错误,这听起来很奇怪,因为最后一个数组元素的引用成员将是未初始化的。实际上,它具有NULL值:

(gdb) p td[2].constNum 
$2 = (const int &) @0x0: <error reading variable>

现在是“魔术”:我将Position_pod改为Position

struct text_descriptor {
    int             id;
    Position_pod    pos;
    const int       &constNum;
};

成为这个:

struct text_descriptor {
    int             id;
    Position        pos;
    const int       &constNum;
};

现在它给出了预期的错误:

error: uninitialized const member ‘text_descriptor::constNum'

我的问题:为什么它在第一种情况下编译,何时应该给出错误(如第二种情况)。 区别在于,Position_pod使用C样式的大括号初始化,而Position使用C ++ 11样式初始化,它调用Position的构造函数。但是,这如何影响将参考成员保持未初始化的可能性?

(更新) 编译: gcc(Ubuntu 4.8.2-19ubuntu1)4.8.2

2 个答案:

答案 0 :(得分:23)

很明显

struct text_descriptor td[3] = {
     {0, {465,223}, 123},
     {1, {465,262}, 123},
};

是列表初始化,初始化列表不为空。

C ++ 11说([dcl.init.list] p3):

  

类型T的对象或引用的列表初始化定义如下:

     
      
  • 如果初始化列表没有元素且T是具有默认构造函数的类类型,则该对象是值初始化的。
  •   
  • 否则,如果T是聚合,则执行聚合初始化(8.5.1)。
  •   
  • ...
  •   

[dcl.init.aggr] P1:

  

聚合是一个数组或类(第9条),没有用户提供的构造函数(12.1),非静态数据成员(9.2)没有大括号或等于初始值,没有私有或受保护的非静态数据成员(第11条),没有基类(第10条),也没有虚函数(10.3)。

td是一个数组,因此它是一个聚合,因此执行聚合初始化。

[dcl.init.aggr] P7:

  

如果列表中的 initializer-clauses 少于聚合中的成员,那么未显式初始化的每个成员都应从空的初始化列表(8.5.4)初始化。

这就是这种情况,因此td[2]是从一个空的初始化列表初始化的,([dcl.init.list] p3再次表示)它是值初始化的。

值初始化反过来意味着([dcl.init] p7):

  

T类型的对象进行值初始化意味着:

     
      
  • 如果T是一个(可能是cv限定的)类类型(第9条),带有用户提供的构造函数(12.1),...
  •   
  • 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T隐式地-declared默认构造函数是非平凡的,构造函数是   调用。
  •   
  • ...
  •   

您的类text_descriptor是一个没有用户提供的构造函数的类,因此td[2]首先进行零初始化,然后调用其构造函数。

零初始化意味着([dcl.init] p5):

  

零初始化类型为T的对象或引用意味着:

     
      
  • 如果T是标量类型(3.9),...
  •   
  • 如果T是一个(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化的,并且填充初始化为零位;
  •   
  • 如果T是(可能是cv认证的)联合类型,......
  •   
  • 如果T是数组类型,...
  •   
  • 如果T是引用类型,则不执行初始化。
  •   

无论text_descriptor的默认构造函数如何,这都是明确定义的:它只是对非引用成员和子成员进行零初始化。

然后调用默认构造函数,如果它是非平凡的。以下是如何定义默认构造函数([special] p5):

  

X默认构造函数是类X的构造函数,可以在没有参数的情况下调用。如果类X没有用户声明的构造函数,则隐式声明没有参数的构造函数   违约(8.4)。隐式声明的默认构造函数是其类的内联公共成员。如果出现以下情况,则将类X的默认默认构造函数定义为已删除:

     
      
  • ...
  •   
  • 任何没有大括号或等号初始化程序的非静态数据成员都是引用类型
  •   
  • ...
  •   
     

如果默认构造函数不是用户提供的,并且如果:

,则默认构造函数是微不足道的      
      
  • 其类没有虚函数(10.3),没有虚基类(10.1)和
  •   
  • 其类的非静态数据成员没有括号或等于初始化程序,
  •   
  • 其类的所有直接基类都有简单的默认构造函数和
  •   
  • 对于类类的所有非静态数据成员(或其数组),每个这样的类都有一个普通的默认构造函数。
  •   
     

否则,默认构造函数是非平凡的。

因此,如果pos是POD类型(!),则按预期删除隐式定义的构造函数它也是微不足道的。因为构造函数是微不足道的,所以不会调用它。因为没有调用构造函数,所以它被删除的事实不是问题。

这是C ++ 11中的一个漏洞,已经修复了。碰巧已修复它以处理inaccessible trivial default constructors,但固定的措辞也涵盖了删除的普通默认构造函数。 N4140(大致是C ++ 14)在[dcl.init.aggr] p7(强调我的)中说:

  
      
  • 如果T是没有用户提供或删除的默认构造函数的(可能是cv限定的)类类型,则该对象为零初始化并且检查默认初始化的语义约束< / strong>,如果   T有一个非平凡的默认构造函数,该对象是默认初始化的;
  •   

作为T.C.在评论中指出,another DR也发生了变化,以便td[2]仍然从空的初始化列表初始化,但现在空的初始化列表意味着聚合初始化。反过来,这意味着每个td[2]的成员也从空的初始化列表初始化([dcl.init.aggr] p7),所以似乎从{初始化参考成员{1}}。

[dcl.init.aggr] p9然后说(正如雷米亚贝尔在一个现已删除的答案中指出的那样):

  

如果不完整或空的初始值列表使未引用的引用类型成员,则该程序格式错误。

我不清楚这是否适用于从隐式{}初始化的引用,但编译器确实将其解释为这样,并且其中没有其他可用的含义。

答案 1 :(得分:-1)

第一个版本(带有_pod后缀的版本)仍然有效,但没有给出错误,因为对于z值,选择了默认值int(0)。同意const int reference。

在第二个版本中,如果不为其赋值,则无法定义const引用。编译器会给你一个错误,因为以后你不能为它分配任何值。

另外,你正在使用的编译器在这里起着重要的作用,也许这是一个bug,只是因为你在int成员之前声明了一个类成员。