虚函数表(vtable)如何处理多继承?基于单继承内存布局的问询
多继承场景下的虚函数表处理
好问题!多继承搭配虚函数的情况确实比单继承要绕一些,咱们结合你给出的例子展开说清楚:
假设我们的代码是这样的:
class Genius { int IQ; public: virtual void brag(); }; class CoolDude { int charm; public: virtual void showOff(); }; class Me : public Genius, public CoolDude { int age; public: // 重写两个基类的虚函数 void brag() override; void showOff() override; };
内存布局变化
和单继承只带一个虚表指针(vptr)不同,每个拥有虚函数的基类,都会在子类实例中对应一个独立的虚表指针。所以Me实例的内存布局大概是这样的:
- 第一个位置:对应
Genius的虚表指针(vptr1),指向Genius相关的虚函数表 - 紧接着:
Genius的成员变量int IQ - 然后:对应
CoolDude的虚表指针(vptr2),指向CoolDude相关的虚函数表 - 紧接着:
CoolDude的成员变量int charm - 最后:
Me自己的成员变量int age
虚函数调用的逻辑
编译器会帮我们处理好不同基类虚函数的调用细节:
- 当通过
Me*或Genius*指针调用brag()时,直接用第一个虚表指针(vptr1)去对应的虚表里找函数入口,此时this指针指向实例的起始位置,不需要调整。 - 当通过
CoolDude*指针调用showOff()时,编译器会自动调整this指针——把它偏移到Me实例中CoolDude部分的起始位置(也就是vptr2的位置),再用这个vptr2去对应的虚表里找showOff()的入口。
如果子类重写了某个基类的虚函数,对应的虚表条目会被替换成子类重写后的函数地址。比如Me::brag()会覆盖vptr1指向的虚表里brag()的条目,Me::showOff()会覆盖vptr2指向的虚表里showOff()的条目。
额外补充:菱形继承的特殊情况
如果遇到菱形继承(比如Genius和CoolDude都继承自同一个带虚函数的基类Person),此时需要用到虚继承来避免基类成员的重复存储,这时除了虚表指针,还会引入虚基表(vbtable)来记录虚基类的位置偏移,不过这是更复杂的衍生场景啦。
内容的提问来源于stack exchange,提问作者user9478968




