C++默认构造函数与成员初始化机制的疑问及测试分析
拆解C++构造函数与成员初始化的那些坑
让我一步步帮你理清这些测试结果背后的C++规则——你碰到的核心是默认初始化和值初始化的区别,还有几个新手很容易踩的语法陷阱。
先搞懂三个关键概念
1. 用户提供的构造函数 vs 编译器生成的构造函数
- 像
C() {}这种你自己写了函数体的,属于用户提供的构造函数; - 像类A的隐式默认构造,或者类B的
B() = default;,属于编译器生成的默认构造函数(=default在类内声明时,不算用户提供)。
2. 默认初始化(Default Initialization)
当你这么声明对象时触发:
- 栈上对象:
A a1; - 堆上对象:
new A
规则是:
- 如果类有用户提供的构造函数,就调用它;如果构造函数里没初始化成员,成员就保持未初始化的垃圾值;
- 如果类只有编译器生成的构造函数,那么成员的初始化方式看自身类型:内置类型(比如
double)会完全不初始化,值是随机的;类类型成员会递归做默认初始化。
3. 值初始化(Value Initialization)
当你这么声明对象时触发:
- 栈上对象:
A a2{};(C++11及以后推荐写法) - 堆上对象:
new A()
规则是:
- 如果类有用户提供的构造函数,调用它(哪怕是空的,成员还是不会自动初始化);
- 如果类只有编译器生成的构造函数,所有成员都会被值初始化:内置类型被置为0,类类型成员值初始化。
逐个分析你的测试案例
先解决两个通用编译错误
你写的A a2();、B b2();、C c2();都不是声明对象,而是声明了一个返回对应类类型、无参数的函数!这就是C里经典的「最令人头疼的解析(Most Vexing Parse)」。要声明默认构造的对象,C11及以后用A a2{};就不会有这个问题了,所以后面.访问成员自然会报错。
类A:无显式构造函数
A a1;:默认初始化,成员r是内置类型,未初始化。VS在Debug模式下会主动检测未初始化的局部变量,所以报编译错误。A* pa1 = new A;:默认初始化,堆上的r未初始化,输出随机垃圾值(比如-6.27744e+66)。A* pa2 = new A();:值初始化,因为A只有编译器生成的构造函数,所以r被值初始化为0,输出0。
类B:显式默认构造函数(B() = default;)
=default的构造函数属于编译器生成的,所以行为和类A完全一致:
B b1;:默认初始化,r未初始化,VS报错。B* pb1 = new B;:默认初始化,r是垃圾值。B* pb2 = new B();:值初始化,r被置为0,输出0。
类C:用户提供的空构造函数(C() {})
这个构造函数是你自己写的,所以不管是默认初始化还是值初始化,都会调用它,但这个构造函数里没有任何初始化r的逻辑,所以r永远是未初始化的垃圾值:
C c1;:调用空构造,r未初始化,输出垃圾值(VS没有报错是因为它认为你写了构造函数,初始化责任在你自己)。C* pc1 = new C;:调用空构造,r未初始化,垃圾值。C* pc2 = new C();:值初始化,但因为有用户提供的构造函数,所以还是调用这个空构造,r依旧是垃圾值。
最后总结几个关键要点
- 如果你希望类的内置成员被自动初始化为0,要么不要写用户提供的构造函数(让编译器生成),要么在构造函数里显式初始化成员(比如
C() : r(0) {}); - 避免「最令人头疼的解析」,声明默认构造的对象用
T obj{};代替T obj();; - VS的Debug模式会检测未初始化的局部变量,但堆上的未初始化变量不会主动报错,只会输出垃圾值;
=default的构造函数在类内声明时,属于编译器生成的,不会触发用户提供构造函数的规则。
内容的提问来源于stack exchange,提问作者Keith M
相关产品推荐
相关产品推荐

