面试题答案
一键面试菱形继承带来的问题
- 数据冗余:因为
C
从A
和B
间接继承了Base
的成员,导致Base
中的数据成员在C
对象中存在两份,浪费内存空间。 - 歧义性:当
C
调用Base
中的成员函数或访问成员变量时,会产生歧义,编译器不知道应该调用从A
继承过来的版本还是从B
继承过来的版本。
解决方法 - 虚继承
通过使用虚继承可以解决菱形继承带来的问题。在A
和B
继承Base
时使用虚继承,即class A : virtual public Base
和class B : virtual public Base
。这样,无论C
从多少条路径继承Base
,Base
在C
对象中都只有一份实例。
虚继承与普通继承在内存布局和实现机制上的差异
- 内存布局:
- 普通继承:在普通继承中,派生类对象内存布局是按照继承关系依次排列基类成员和派生类新增成员。对于菱形继承,如果不使用虚继承,
Base
成员会在C
对象中出现多次。 - 虚继承:虚继承下,派生类对象中会有一个虚基表指针(vptr),它指向一个虚基表(vbtable)。虚基表中记录了虚基类相对于派生类对象起始地址的偏移量。通过这种方式,即使从多个路径继承虚基类,也能保证虚基类只有一份实例,节省内存空间。
- 普通继承:在普通继承中,派生类对象内存布局是按照继承关系依次排列基类成员和派生类新增成员。对于菱形继承,如果不使用虚继承,
- 实现机制:
- 普通继承:普通继承关系简单直接,编译器在编译时就能确定成员的访问偏移地址。
- 虚继承:虚继承需要额外的运行时支持。当访问虚基类成员时,程序需要通过虚基表指针找到虚基表,再从虚基表中获取虚基类成员的偏移量,进而访问到正确的成员。这增加了访问的开销,但解决了菱形继承的数据冗余和歧义问题。