这个C ++成员初始化行为是否定义正确?

时间:2018-08-27 08:25:04

标签: c++ constructor initialization undefined-behavior default-constructor

让我们假设我们有一个类B,它有一个member,默认情况下初始化为42。此类知道如何打印其member的值(它在构造函数中这样做):

struct B
{
  B() : member(42) { printMember(); }

  void printMember() const { std::cout << "value: " << member << std::endl; }

  int member;
};

然后,我们添加一个类A,该类接收对B的const引用,并要求B打印其值:

struct A
{
  A(const B& b) { b.printMember(); }
};

最后,我们添加另一个类Aggregate,该类将AB聚合在一起。棘手的部分是类型{{1}的对象a在类型A的对象b之前被声明,但是随后B使用(尚未生效? )对a的引用:

b

考虑创建struct Aggregate { A a; B b; Aggregate() : a(b) { } }; 的输出(我已经为AggregateA的构造函数和析构函数添加了一些日志记录)(Try it online!):

B

我是否可以正确地假设引用a c'tor value: 0 b c'tor value: 42 b d'tor a d'tor 实例(尚未有效)初始化a是无效的,因此这是未定义的行为吗?


我知道初始化顺序。这就是让我挣扎的原因。我知道 b尚未构建,但我也认为知道b的未来地址甚至可以在{{ 1}}被构造。因此,我认为可能存在一些我不知道的规则 ,该规则允许编译器在构造b或类似的东西之前默认初始化b的成员。 (如果第一个打印出的值看起来像是随机的而不是b(默认值b,那会更明显)。


This answer帮助我了解到我需要区别对待

  • 将引用绑定到未初始化的对象(有效),并且
  • 通过引用访问未初始化的对象(未定义)

2 个答案:

答案 0 :(得分:23)

是的,它是UB是正确的,但是出于不同的原因,而不是仅仅存储对尚未构造的对象的引用。

类成员的构建按其在类中的出现顺序进行。尽管B的地址不会改变,并且从技术上讲您是can store a reference to it,如@StoryTeller所指出的那样,使用尚未构建的b.printMember()在构造函数中调用b绝对是UB。

答案 1 :(得分:1)

类成员的初始化顺序如下。

根据CPP标准(N4713),相关部分突出显示:

  

15.6.2初始化基和成员[class.base.init]   ...
  13在非委托构造函数中,初始化按以下顺序进行:
  (13.1)—首先,并且仅对于最派生类(6.6.2)的构造函数,将虚拟基类按照它们在基的有向无环图的深度优先从左到右遍历时出现的顺序进行初始化类,其中“从左到右”是基类在派生类base-specifier-list中的出现顺序。
  (13.2)—然后,直接基类按照它们出现在base-specifier-list中的声明顺序进行初始化(与mem-initializer的顺序无关)。
  (13.3)— 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样,无论mem-initializer的顺序如何)。
  (13.4)—最后,执行构造函数主体的复合语句。
  [注意:声明顺序是强制执行的,以确保以相反的初始化顺序销毁基础和成员子对象。 —尾注]