面向对象编程:基类构造初始化差异及派生类初始化疑问
问题解答
一、A类构造函数两种实现的区别
咱们先拆解这两种写法的核心差异,尤其是结合你的代码里参数名和成员名都是val的情况:
初始化列表写法
A::A(int val) : val(val) {}- 这是C++里成员初始化的标准正确姿势:初始化列表会在类成员(包括基类成员、当前类的成员)的构造阶段直接完成初始化。这里的
val(val)左边是类的成员变量A::val,右边是构造函数的参数val,编译器能准确区分两者,所以成员val会被直接设置为传入的参数值,没有多余的中间步骤。 - 如果成员是类类型(比如
std::string或其他带自定义构造函数的类),初始化列表会直接调用该成员的对应构造函数,效率更高;就算是int这类内置类型,也能保证它被明确初始化,不会出现未定义的随机值。
- 这是C++里成员初始化的标准正确姿势:初始化列表会在类成员(包括基类成员、当前类的成员)的构造阶段直接完成初始化。这里的
构造函数体内赋值写法
A::A(int val) {val = val;}- 这里藏了一个致命的坑:因为参数名和成员名重名了,函数体内的
val = val,两边的val都是构造函数的局部参数,相当于把参数自己赋值给自己,类的成员变量A::val完全没被赋值,它的初始值是未定义的(内置类型未初始化时是随机垃圾值)。 - 退一步说,就算参数名和成员名不重名,这种写法也是先默认初始化成员(内置类型是未定义值,类类型会调用默认构造函数),再执行赋值操作,比初始化列表多了一步,效率更低;而且对于没有默认构造函数的类类型成员,这种写法根本编译不过。
- 这里藏了一个致命的坑:因为参数名和成员名重名了,函数体内的
总结:初始化列表是直接初始化成员,效率高且能避免重名导致的错误;体内赋值容易踩重名的坑,还可能有额外性能开销,甚至无法初始化某些类型的成员。
二、B类构造函数无法编译的原因
你的B类继承自A类,而A类没有默认构造函数(A只提供了一个带int参数的构造函数)。根据C++的规则:
- 当派生类的构造函数没有在初始化列表里显式调用基类的构造函数时,编译器会自动尝试调用基类的默认构造函数来初始化基类部分。
- 但你的A类有自定义构造函数,编译器不会为它生成默认构造函数,所以编译器找不到可以调用的基类构造函数,自然会报错。
正确的B类构造函数应该写成这样,在初始化列表里显式调用A的构造函数:
B::B(int val, int extra) : A(val), extra(extra) {}
另外还要提一句:你在B类里写了int get_val() = 0;,这会把B也变成抽象类(因为存在纯虚函数),如果想要实例化B对象,必须实现get_val()函数,去掉=0的部分,比如:
int get_val() override { return val; }
内容的提问来源于stack exchange,提问作者Athena
相关产品推荐
相关产品推荐

