取未初始化对象成员的地址是否为定义良好行为?附代码实例问询
这个问题的答案很明确:这段代码的行为是未定义的,不属于合法且定义良好的行为。下面来拆解原因和问题所在:
1. C++构造初始化的固定顺序
首先要明确C++中派生类对象的构造流程:
- 当构造
bar对象时,会先调用基类foo的构造函数,完成基类的初始化; - 之后才会初始化
bar的非静态成员my_member。
这个顺序是由C++标准强制规定的,和你在初始化列表中写的顺序(foo(&my_member.y), my_member(42))完全无关——哪怕你把my_member的初始化写在前面,基类构造依然会先执行。
2. 核心问题:引用未构造对象的成员
在bar的构造初始化列表中,我们向foo的构造函数传入了&my_member.y,但此时my_member这个member类型的对象还没有开始构造(它的构造函数要等foo构造完成后才会运行)。
根据C++标准的[class.cdtor]/1条款:
对于拥有非平凡构造函数的对象,在其构造函数开始执行之前,引用该对象的任何非静态成员或基类,都会导致未定义行为。
这里的member类拥有用户自定义的构造函数(属于非平凡构造函数),所以在它的构造函数执行前,取&my_member.y(本质是引用它的成员y)的操作已经触发了未定义行为。
3. 未定义行为的影响
哪怕后续my_member被正确初始化为42,main中解引用地址时看起来能输出预期值,但未定义行为意味着程序的行为完全不受标准保障——编译器可以任意优化这段代码、输出随机值、甚至导致程序崩溃,一切结果都是不可预测的。
如何修正?
要避免这种问题,必须确保在引用成员对象的任何部分之前,该成员对象已经完成初始化。常见的修正方案有两种:
方案1:基类提供后续设置指针的接口
修改foo类,增加默认构造函数和指针设置方法,在my_member初始化完成后再赋值:
struct foo { foo() : x(nullptr) {} // 默认构造函数 void set_x(int* p_x) { x = p_x; } int* x; }; struct bar : foo { bar() : my_member(42) { set_x(&my_member.y); // 此时my_member已经构造完成,引用成员是安全的 } member my_member; };
方案2:调整类结构(如果允许)
如果业务逻辑允许,可以把my_member改为bar的基类(基类初始化顺序是按继承声明顺序来的),这样my_member会先于foo初始化:
struct bar : member, foo { bar() : member(42), foo(&y) {} // 此时member已经初始化,&y是安全的 };
内容的提问来源于stack exchange,提问作者François Andrieux

