【问题标题】:C++11 strange brace initialization behaviorC++11奇怪的大括号初始化行为
【发布时间】:2015-02-09 11:40:06
【问题描述】:

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

【问题讨论】:

  • 编译器版本很重要。 Clang 3.5 and GCC 4.9.2 不要编译这个。
  • 看起来像一个编译器错误。我可以在 GCC 4.8 及更早版本上重现,所以看起来它已在 4.9 中修复。
  • @remyabel GCC 4.9.2 确实使用-std=c++03 编译它。
  • @PavelOganesyan:该类中有一个默认构造函数。
  • 请致电语言律师。

标签: c++ c++11


【解决方案1】:

很明显

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) 中初始化。 p>

这里就是这种情况,所以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):

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 限定的)类类型,则该对象为零初始化并检查默认初始化的语义约束,如果 T 有一个重要的默认构造函数,对象是默认初始化的;

作为 T.C.在 cmets 中指出,another DR 也发生了变化,因此 td[2] 仍然从一个空的初始化程序列表初始化,但是这个空的初始化程序列表现在意味着聚合初始化。反过来,这意味着td[2] 的每个成员也是从一个空的初始化列表初始化的(再次是[dcl.init.aggr]p7),所以似乎从{} 初始化引用成员。

[dcl.init.aggr]p9 然后说(正如 remyabel 在现已删除的答案中指出的那样):

如果一个不完整的或空的 initializer-list 使引用类型的成员未初始化,则程序是不正确的。

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

【讨论】:

  • 引用不能被值初始化。
  • @remyabel 我知道,但是类的值初始化并不意味着该类成员的值初始化。
  • @bogdan 它确实有一个默认构造函数。该默认构造函数被删除,因此如果使用该默认构造函数是错误的,但由于措辞上的差距,它甚至没有被使用。我引用了标准中的 [special]p5,它指定了默认构造函数的定义时间以及定义方式。
  • C++14 还交换了 [dcl.init.list]p2 的前两个项目符号,因此无论初始化列表是否为空,它始终是聚合初始化。
  • @T.C.我在编辑中解决了这个问题,但我不清楚该程序在 C++14 中是否有效。你对此有什么想法吗?
【解决方案2】:

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

在第二个版本中,你不能在不给它一个值的情况下定义一个 const 引用。编译器给你一个错误,因为以后你不能给它赋值。

另外,你使用的编译器在这里扮演着重要的角色,也许这是一个错误,只是因为你在 int 成员之前声明了一个类成员。

【讨论】:

  • 但是两个版本(尝试)都初始化一个引用而不给它一个值。问题在于数组的初始化器数量,而不是每个数组元素的 Position 成员。 (并回答您的问题:演示如果您将该成员的类型从 POD 更改为非 POD,编译器的行为将如何变化。)
  • @MikeSeymour 这是一个编译器问题。而且您还在结构中包含一个类。如果你保持结构简单,或者在第一个结构中使用构造函数会更好
  • “这是一个编译器问题”——看起来是这样。但你的回答并没有这么说,而且似乎说第一个版本是格式良好的,即使它不是。
  • 可能是排序错误..因为您在 int 之前定义了一个类成员
猜你喜欢
  • 2021-12-06
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
  • 2011-02-22
  • 1970-01-01
  • 2020-02-08
  • 1970-01-01
相关资源
最近更新 更多