面试题答案
一键面试代码实现
#include <iostream>
class A {
public:
int value;
A(int v) : value(v) {
std::cout << "A constructor with value: " << value << std::endl;
}
};
class B : virtual public A {
public:
B(int v) : A(v) {
std::cout << "B constructor" << std::endl;
}
};
class C : virtual public A {
public:
C(int v) : A(v) {
std::cout << "C constructor" << std::endl;
}
};
class D : public B, public C {
public:
D(int v) : A(v), B(v), C(v) {
std::cout << "D constructor" << std::endl;
}
};
可以通过以下方式测试:
int main() {
D d(10);
return 0;
}
设计原因
- 避免菱形继承的二义性和数据冗余:在菱形继承结构(A -> B -> D, A -> C -> D)中,如果不使用虚继承,D会包含两份A的成员,这会导致数据冗余和访问A成员时的二义性。虚继承使得从不同路径继承过来的虚基类(A)在最终的派生类(D)中只存在一份实例。
- 构造参数传递:在虚继承体系中,最底层的派生类(D)负责调用虚基类(A)的构造函数。这是因为只有最底层的派生类知道应该使用什么样的参数来构造虚基类,从而保证虚基类的成员被正确初始化。B和C虽然也继承自A,但由于虚继承,它们不再负责初始化A,而是由D来完成。
不同传递方式可能带来的问题
- 不使用虚继承:
- 数据冗余:如上述提到,最终派生类D会包含两份A的成员,浪费内存空间。
- 访问二义性:当访问A的成员时,编译器不知道应该使用从B继承过来的A的成员还是从C继承过来的A的成员,导致编译错误。
- 错误的构造参数传递:
- 虚基类未初始化:如果最底层的派生类D没有显式调用虚基类A的构造函数,A的成员将不会被正确初始化,导致运行时错误。
- 多重初始化:如果B和C都在自己的构造函数中尝试初始化A(在虚继承体系下不应如此),会导致A被多次初始化,这也是不允许的,会产生未定义行为。