面试题答案
一键面试多重继承下对象间数据共享的复杂问题
- 数据冗余:在菱形继承结构中,从公共基类派生的多个中间类会各自保留一份公共基类的数据成员副本。例如,若有一个基类
A
,两个派生类B
和C
都继承自A
,然后D
同时继承自B
和C
,那么D
中就会有两份A
的数据成员副本,造成内存浪费。 - 二义性:当
D
类对象访问来自公共基类A
的成员时,由于存在两份副本,编译器不知道应该访问哪一份,从而产生二义性错误。例如,若A
中有一个成员函数func()
,D
类对象调用func()
时就会出现编译错误。
虚继承解决问题的机制
虚继承通过让中间派生类共享一份公共基类的子对象来解决数据冗余和二义性问题。在虚继承中,虚基类在最终派生类中只存在一份实例。编译器会使用特殊的机制来确保虚基类子对象的唯一性。当从多个路径继承虚基类时,虚基类的构造函数由最终派生类负责调用,而不是由中间派生类调用,这样就保证了虚基类只被初始化一次。
代码示例
#include <iostream>
// 公共基类
class A {
public:
int data;
A(int value) : data(value) {}
};
// 中间派生类 B 虚继承自 A
class B : virtual public A {
public:
B(int value) : A(value) {}
};
// 中间派生类 C 虚继承自 A
class C : virtual public A {
public:
C(int value) : A(value) {}
};
// 最终派生类 D 继承自 B 和 C
class D : public B, public C {
public:
D(int value) : A(value), B(value), C(value) {}
};
int main() {
D obj(10);
std::cout << "Value of data in D: " << obj.data << std::endl;
return 0;
}
关键实现细节解释
- 虚继承声明:在
B
和C
类的继承声明中使用virtual
关键字,声明它们虚继承自A
类。这样就告诉编译器,B
和C
类不会各自保留一份A
类的副本,而是共享一份。 - 构造函数调用:在
D
类的构造函数初始化列表中,直接调用A
类的构造函数。这是因为虚基类由最终派生类负责初始化,确保A
类只被初始化一次。虽然在B
和C
的构造函数中也可以调用A
的构造函数,但在最终派生类D
中再次调用是必要的,以保证虚基类的正确初始化。 - 数据访问:在
main
函数中,创建D
类对象obj
并访问其data
成员,由于虚继承的机制,不会出现数据冗余和二义性问题,可以正常访问和使用公共基类A
的数据成员。