面试题答案
一键面试虚函数表结构
- 每个基类虚函数表:每个含有虚函数的基类都有自己的虚函数表。在多继承时,派生类会为每个基类的虚函数表维护一份独立的副本。
- 派生类虚函数表:派生类的虚函数表实际上是多个基类虚函数表的集合。如果派生类重写了某个基类的虚函数,那么在对应基类虚函数表中该虚函数的位置会被替换为派生类重写的函数地址。
多态调用过程
- 编译期:编译器根据指针或引用的静态类型,确定可能调用的虚函数的入口,即虚函数表的索引。
- 运行期:根据对象的实际类型,找到对应的虚函数表。然后通过之前确定的虚函数表索引,在虚函数表中找到实际要调用的函数地址,并执行该函数。例如,假设有
Base1
、Base2
两个基类,Derived
派生类继承自这两个基类。如果通过Base1* ptr = new Derived();
来调用虚函数,编译器先根据Base1
的类型确定虚函数在Base1
虚函数表中的索引,运行时ptr
实际指向Derived
对象,找到Derived
中Base1
对应的虚函数表,再根据索引找到实际函数地址调用。
可能遇到的问题
- 菱形继承问题:当出现菱形继承结构时(例如
A
为基类,B
和C
继承自A
,D
同时继承自B
和C
),会导致数据冗余和二义性。因为D
会继承两份A
的成员,在访问A
的成员时会出现二义性。 - 虚函数表指针的管理复杂性:由于存在多个虚函数表,虚函数表指针的维护和管理变得复杂,可能导致内存布局的复杂性增加,尤其是在对象创建、销毁和内存移动等操作时。
解决方法
- 虚继承解决菱形继承:在继承关系中使用虚继承,即
class B : virtual public A
和class C : virtual public A
。这样D
只会继承一份A
的成员,避免了数据冗余和二义性问题。 - 合理设计类结构:尽量简化类的继承层次,避免过度复杂的多继承结构。如果可能,优先考虑使用组合(composition)替代多继承,以降低代码复杂性和维护成本。