为什么当它打破初始化列表的顺序规则时它会起作用
Why does it work when it breaks the rule of order of initialization list
为什么这段代码有效?我预计这会失败,因为违反了基本的C++规则之一:
#include <iostream>
using namespace std;
struct A {
A() { cout << "ctor A" << endl; }
void doSth() { cout << "a doing sth" << endl; }
};
struct B {
B(A& a) : a(a) { cout << "ctor B" << endl; }
void doSth() { a.doSth(); }
A& a;
};
struct C {
C() : b(a) { cout << "ctor C" << endl; }
void doSth() { b.doSth(); }
B b;
A a;
};
int main()
{
C c;
c.doSth();
}
https://wandbox.org/permlink/aoJsYkbhDO6pNrg0
我预计这会失败,因为在 C 的构造函数中,当尚未创建此 A 对象时,B 会引用 A 的对象。
我错过了什么吗?初始化顺序规则是否与字段顺序相同,不适用于引用?
编辑: 更让我惊讶的是,我可以在 B 构造函数中添加对 "a.doSth();"的调用,这也将起作用。为什么?此时,A 对象应该不存在!
只要B
的构造函数不使用它获得的引用来绑定其成员以外的任何内容,您的代码就可以了。当C
的c'tor启动时,a
的存储已经分配,就像Sneftel所说的那样,它在范围内。因此,您可以参考它,因为 [basic.life]/7 明确允许:
同样,在对象的生存期开始之前,但在 对象将占用的存储已分配,或者在 对象的生存期已经结束,并且在存储之前 占用的对象被重复使用或释放,任何引用 可以使用原始对象,但只能以有限的方式使用。对于对象 正在建设或破坏中,请参见[class.cdtor]。否则,这样的 GL值是指分配的存储 ([basic.stc.dynamic.deallocation]),并使用 不依赖于其值的glvalue是明确定义的。该计划 在以下情况下具有未定义的行为:
- GL值用于访问对象,或
- glvalue 用于调用对象的非静态成员函数,或者
- GL值绑定到对虚拟基类([dcl.init.ref])的引用,或者
- glvalue 用作 dynamic_cast 的操作数或 typeid 的操作数。
关于您的编辑:
更让我惊讶的是,我可以在 B 构造函数中添加对 "a.doSth();"的调用,这也将起作用。为什么?此时,A 对象应该不存在!
未定义的行为是未定义的。我链接到的段落中的第二个项目符号几乎说明了这一点。编译器可能足够聪明地捕捉到它,但并非必须如此。
在代码片段中,构造C
时,a
尚未初始化,但它已在范围内,因此编译器不需要发出诊断。其值未定义。
从某种意义上说,代码很好,因为B::a
正确地是C::a
的别名。存储支持C::a
的生存期在B::B()
运行时已经开始。
关于您的编辑:尽管C::a
的存储持续时间已经开始,但B::B()
的a.doSth()
绝对会导致未定义的行为(谷歌看看为什么某些东西可以是 UB 并且仍然"工作")。
这是因为您在初始化期间没有访问未初始化C::b
字段C::a
。通过调用C() : b(a)
将引用绑定到要为构造函数提供的a
B(A& a)
。如果您以某种方式更改代码以实际使用未初始化的值,那么它将是一个未定义的行为:
struct B {
B(A& a)
: m_a(a) // now this calls copy constructor attempting to access uninitialized value of `a`
{ cout << "ctor B" << endl; }
void doSth() { a.doSth(); }
A m_a;
};
未定义的行为意味着一切皆有可能,包括看起来工作正常。这并不意味着它下周甚至下次运行它时都会正常工作 - 你可能会让恶魔从你的鼻子里飞出来。
当你调用a.doSth()
时,可能发生的事情是编译器将调用转换为静态a::doSth()
;因为它不是一个虚函数,所以它不需要访问对象来进行调用。函数本身不使用任何成员变量或函数,因此不会生成无效访问。即使不能保证有效,它也能工作。
它不"工作",因为用于初始化的a
对象尚未调用其构造函数(您的日志显示) - 这意味着b
的初始化可能会也可能不会失败,具体取决于a
正在做什么。
编译器不会阻止这一点,但我想它应该。无论如何,我不认为这是UB,除非您实际尝试使用单元化对象;仅存储引用应该没问题。
它之所以有效B
是因为它是用引用初始化的,并且该引用已经存在,因此可用于使用它初始化某些内容。
如果您尝试在 ctor 中按值传递a
B
则编译器会抱怨:
警告:此处使用字段"A"时未初始化 [-文初始化]
- CMake-按正确顺序将项目与C运行时对象文件链接
- 函数调用中参数的顺序重要吗
- 为什么不;名字在地图上是按顺序排列的吗
- 将Integer转换为4字节的unsined字符矢量(按大端字节顺序)
- 此代码是否违反一个定义规则
- 数到第n个楼梯的路(顺序无关紧要)
- 优先顺序:智能指针和类析构函数
- 生成文件不对文件使用隐式规则
- 在循环中按顺序遍历成员变量
- 独立读取-修改-写入顺序
- QML按钮点击功能执行顺序
- 变量可能尚未初始化[MIRA 2012规则9.1,强制性]
- 静态结构和一个定义规则
- C++中数据类型修饰符的顺序
- 当比特(而不是字节)的顺序至关重要时的持久性
- C++从其他 constexpr 创建 lambda 不能按顺序执行 Constexpr
- 为什么当它打破初始化列表的顺序规则时它会起作用
- 优先顺序规则
- 按相反顺序排序。 "Don't repeat yourself"规则
- 有人能给我解释一下模板解析顺序规则吗?