繁体   English   中英

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

[英]C++11 strange brace initialization behavior

我不明白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

很明显

 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-clause少于聚合中的成员,则未显式初始化的每个成员都应从空的初始化列表(8.5.4)初始化。

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

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

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

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

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

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

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

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

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

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

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

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

如果默认构造函数不是用户提供的,则默认构造函数是微不足道的,如果:

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

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

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

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

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

正如TC在评论中指出的那样, 另一个DR也发生了变化,因此td[2]仍然是从一个空的初始化列表初始化,但是这个空的初始化列表现在意味着聚合初始化。 反过来,这意味着每个td[2]的成员也从一个空的初始化列表初始化([dcl.init.aggr] p7),所以似乎从{}初始化了引用成员。

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

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

我不清楚这适用于从隐式{}初始化的引用,但编译器确实将其解释为这样,并且没有太多其他可能意味着它。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM