面试题答案
一键面试虚拟继承原理
- 虚拟基类存储方式:虚拟继承中,对象会额外存储一个指针(通常称为虚基表指针),该指针指向一个虚基表。虚基表记录了虚拟基类相对于派生类对象起始地址的偏移量。这样,通过这个偏移量,在需要访问虚拟基类成员时,就可以正确定位到虚拟基类的位置,从而避免了数据冗余。
- 构造函数调用顺序:在虚拟继承体系中,构造函数调用顺序为:
- 首先调用虚拟基类的构造函数,且在整个继承体系中,无论虚拟基类被继承多少次,都只调用一次。
- 然后按照继承列表中声明的顺序调用非虚拟基类的构造函数。
- 最后调用派生类自身的构造函数。
在菱形继承中避免数据冗余和二义性
考虑以下菱形继承结构:
class A {
public:
int data;
A(int value) : data(value) {}
};
class B : virtual public A {
public:
B(int value) : A(value) {}
};
class C : virtual public A {
public:
C(int value) : A(value) {}
};
class D : public B, public C {
public:
D(int value) : A(value), B(value), C(value) {}
};
代码分析
- 数据冗余:在上述代码中,如果不使用虚拟继承,
D
对象中会包含两份A
类的数据成员data
,造成数据冗余。而使用虚拟继承后,D
对象中只包含一份A
类的数据成员data
,避免了数据冗余。 - 二义性:如果不使用虚拟继承,当
D
类对象访问A
类成员时,会出现二义性,因为编译器不知道应该访问从B
继承过来的A
还是从C
继承过来的A
。而通过虚拟继承,无论从B
还是C
继承,A
类只有一份实例,从而避免了二义性。例如:
#include <iostream>
int main() {
D obj(10);
std::cout << obj.data << std::endl; // 不会出现二义性
return 0;
}
在这个例子中,D
类对象obj
可以直接访问data
成员,不会产生二义性,并且数据也不会冗余。