我不明白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
答案 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成员之前声明了一个类成员。