为何违反C++成员初始化顺序规则的代码可正常运行?
为啥这段代码看似能正常跑?其实是踩了未定义行为的坑!
嘿,这个问题其实是C++里一个非常容易踩的隐蔽坑,先给你把核心逻辑掰明白:
首先,记住一条铁律:类成员的初始化顺序只和它们在类里的声明顺序有关,和构造函数初始化列表的顺序半毛钱关系都没有——这条规则对引用成员同样生效,没有任何例外。
咱们来看你的C结构体:
struct C { C() : b(a) { cout << "ctor C" << endl; } void doSth() { b.doSth(); } B b; // 第一个声明的成员 A a; // 第二个声明的成员 };
按照规则,创建C对象时,初始化顺序是:
- 先初始化
B b(因为它是第一个被声明的成员) - 再初始化
A a(第二个声明的成员)
这就意味着,在B的构造函数里,你引用的a是一个还没被构造的A对象——这在C++标准里属于未定义行为(Undefined Behavior)。
那为啥代码没崩溃甚至能输出结果?
未定义行为的恶心之处就在这:它可能“看起来正常”,也可能直接炸锅,完全看运气和编译器/运行环境的脸色。
在你的例子里:
A的构造函数只是输出字符串,没有复杂的成员初始化;doSth()方法也只是简单打印,根本没访问A的任何成员变量(因为A本来就没成员变量)。
这种情况下,即使A对象没构造,调用它的非虚成员函数(且不碰成员变量)时,编译器生成的代码可能直接执行函数体——毕竟非虚成员函数的调用不依赖对象的构造状态,只要引用的地址是有效的(这里a的内存已经被分配了,只是对象没初始化)。
但这绝对不是“合法”的操作!要是A有成员变量,或者doSth()里访问了成员变量,代码大概率会崩溃或者输出莫名其妙的结果。
怎么修复?
太简单了,调整C里成员的声明顺序,把A a放在B b前面就行:
struct C { C() : b(a) { cout << "ctor C" << endl; } void doSth() { b.doSth(); } A a; // 先声明A,确保先完成构造 B b; // 再用已经构造好的a初始化B };
这样初始化顺序就变成先构造A a,再用合法的a初始化B b,行为就完全符合预期了。
内容的提问来源于stack exchange,提问作者YotKay
相关产品推荐
相关产品推荐

