面试题答案
一键面试C类对象的虚函数表结构
- 一般多重继承情况
- 由于C类同时继承自A和B,C类对象会有两个虚函数表指针(假设编译器采用这种布局方式),分别对应基类A和基类B的虚函数表。
- 虚函数表中,对于C类覆盖的虚函数,会在虚函数表中相应位置替换为C类的函数指针。未覆盖的虚函数,依然保留基类的函数指针。例如,如果C类覆盖了A类的
virtual void funcA()
,那么对应A类虚函数表中funcA
的位置,存储的是C类funcA
的函数指针;若C类没有覆盖B类的virtual void funcB()
,B类虚函数表中funcB
的位置,存储的是B类funcB
的函数指针。
- 菱形继承情况
- 假设存在菱形继承结构,D类从C类(C类多重继承自A和B)公有继承。在这种情况下,C类的虚函数表结构基本不变,但D类会有更复杂的情况。
- 如果D类覆盖了一些虚函数,这些虚函数的函数指针会替换到相应虚函数表(A或B对应的虚函数表)中的位置。同时,由于菱形继承可能会导致虚基类的引入(为了避免数据冗余),虚函数表可能会增加一些与虚基类相关的调整信息,例如用于定位虚基类子对象的偏移量等。
从虚函数表输出内容分析函数调用过程
- 一般多重继承
- 当调用C类对象的虚函数时,例如
c.funcA()
(假设funcA
是A类的虚函数且C类覆盖了它),首先通过C类对象的第一个虚函数表指针(对应A类虚函数表)找到A类虚函数表。 - 在虚函数表中,根据
funcA
在虚函数表中的索引位置,获取到C类funcA
的函数指针。 - 然后通过该函数指针调用C类的
funcA
函数。
- 当调用C类对象的虚函数时,例如
- 菱形继承
- 对于D类对象调用虚函数,例如
d.funcA()
(假设funcA
是A类的虚函数且经过多重继承和覆盖)。 - 首先根据D类对象的虚函数表指针(对应A类虚函数表方向)找到虚函数表。
- 虚函数表中可能会有一些额外信息用于处理虚基类相关的调整。如果
funcA
被D类覆盖,虚函数表中相应位置存储的是D类funcA
的函数指针,通过该指针调用D类的funcA
;如果未被D类覆盖但被C类覆盖,则找到C类funcA
的函数指针并调用C类的funcA
。如果都未覆盖,则找到A类funcA
的函数指针调用A类的funcA
。同时,虚函数表中的额外信息会协助处理虚基类相关的地址调整,以确保在复杂继承结构下能够正确定位函数和相关对象数据。
- 对于D类对象调用虚函数,例如