考虑:
struct A { void do_something() {} };
struct B {
A& a;
B(A& a) : a{a} { a.do_something(); }
};
struct C {
A a; B b;
C(A& a) : b{a} {}
};
int main() {
C c{ c.a };
}
这个可能可能会起作用,因为:
c
初始化之前,我们也知道它的内存布局和c.a
的地址c.a
,直到初始化为止。此外,我没有在几个不同的编译器下收到警告。
然而,我经历了一些非常奇怪的行为(稍后会发生),只能做一些未定义的事情,并且当我重新组织我的程序以避免这种模式时,它才会消失。
感谢。
答案 0 :(得分:2)
您的程序具有未定义的行为,因为您正在访问对象外部的对象成员,并且在对象的生命周期开始之前。
$12.7: 1:对于具有非平凡构造函数的对象, 引用之前对象的任何非静态成员或基类 构造函数以未定义的行为开始执行结果...
至于为什么编译好:在声明之后可以使用名称...并引用C ++标准草案......(强调我的):
$3.3.2 : 1:名称的声明点是立即的 在完整的声明者(条款[dcl.decl])和之前之后 initializer(如果有的话)......
initalizer被定义为具有语法(部分复制):
初始化 ...
initializer: brace-or-equal-initializer ( expression-list ) brace-or-equal-initializer: = initializer-clause braced-init-list initializer-clause: assignment-expression braced-init-list . . .
上述原因与编译原因相同:
int k(k);
int m{m};
int b = b;
答案 1 :(得分:2)
除了我之前的回答,
你的代码是一个狡猾的代码...因为,尽管假设 UB在初始化之前使用了一个对象,代码的行为显然 定义良好。
如何?
在c
的构造中,将发生以下事件序列:
您调用C
的构造函数C(A& a) : b{a} {}
,该构造函数引用A
类型的对象。 (引用就像一个地址,正如您正确提到的,c.a
的地址在编译时是已知的)。您的电话是C c{ c.a };
,编译器也可以,因为c.a
是一个可访问的名称
由于C
成员的声明顺序......
struct C {
A a; B b;
C(A& a) : b{a} {}
};
对象a
在b
之前初始化。
因此,a
在成员初始化程序中使用之前就会生效... b{a}
但同样,你可以通过optimizers ...
来吸烟答案 2 :(得分:0)
C ++有一个非常明确的构造顺序。初始化第一(非虚拟)基类。接下来按照它们在类中声明的顺序初始化成员(并且忽略初始化列表的顺序),并且只有在最后一个成员被初始化之后才会输入构造函数的主体。 / p>
在这种情况下,这意味着按照A::A, B::B, C::C
的顺序调用ctors。
如果C被声明为
,会会出现问题struct C {
B b; A a;
C(A& a) : b{a} {}
};