【问题标题】:Is this C++ member initialization behavior well defined?这个 C++ 成员初始化行为是否定义良好?
【发布时间】:2019-02-01 18:08:09
【问题描述】:

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

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

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

  int member;
};

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

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

最后我们添加另一个类Aggregate,它聚合了AB。棘手的部分是A 类型的对象a 在对象b 类型B 之前声明,但随后a 使用(尚未有效?)对b 的引用进行初始化:

struct Aggregate
{
  A a;
  B b;

  Aggregate() : a(b) { }
};

考虑创建Aggregate 的输出(我在AB 的构造函数和析构函数中添加了一些日志记录)(Try it online!):

a c'tor
value: 0
b c'tor
value: 42
b d'tor
a d'tor

我是否可以假设使用 b 的(尚未有效的)实例来初始化 a 是无效的,因此这是未定义的行为?


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


This answer帮助我明白了我需要区分

  • 绑定一个引用到一个未初始化的对象(这是有效的)和
  • 通过引用访问一个未初始化的对象(未定义)

【问题讨论】:

  • 如果您在类定义中切换它们的顺序,它将具有更好的定义行为。或者,不要使用引用来访问对象(有关相关问题,请参阅此处stackoverflow.com/questions/50020255/…)。
  • 我知道如何“修复”它。我不确定的是,它是应该被修复(因为它很丑)还是必须被修复(因为它是未定义的行为,因此被破坏了);)跨度>
  • 您显示的内容已损坏。另一个问题中的例子不是。这是一个标准的雷区
  • 您在初始化之前使用对象。为什么你认为你不应该修复它?
  • b 无效。 b的引用 是有效的,但它是对尚未构造的对象的引用;它可以以某些有限的方式使用,但不能以您使用它的方式。

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


【解决方案1】:

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

类成员的构造按照它们在类中出现的顺序进行。虽然B 的地址不会改变,从技术上讲你can store a reference to it,正如@StoryTeller 指出的那样,在构造函数中调用b.printMember() 和尚未构造的b 肯定是UB。

【讨论】:

    【解决方案2】:

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

    来自 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-initializers 的顺序无关)。
    (13.4) — 最后,执行构造函数主体的复合语句。
    [注意:声明顺序是为了确保基子对象和成员子对象以初始化的相反顺序被销毁。 ——尾注]

    【讨论】:

    • By the time, the constructor of Aggregate is called, the constructors of a (first) and b (next) are already called — 你似乎混淆了AggregateA,或者别的什么。该声明似乎没有意义。
    • @Ruslan:已编辑,看看现在是否正确。
    • 不,我还是不明白你说的那句话是什么意思。究竟什么不是未定义的行为?在初始化完成时,UB 已经发生了——在调用a 的构造函数的阶段。
    • 是的,UB在建a时发生过。但是在其构造之后,member 具有一定的价值,无论该价值是什么。因此,在 Aggregate 的构造中使用该值不是 UB。但整体行为是UB。
    • 不,它没有。在 UB 之后,您无法分辨程序的状态 - 程序可能不再存在,或者似乎同时处于多个状态或其他什么...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-07
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 2021-09-19
    相关资源
    最近更新 更多