在 C++ 的多重继承 中,虚表(vtable)结构会变得更加复杂。
一、基础回顾:单继承下的虚表结构
- 类中含有虚函数 → 编译器生成虚表(每类一张);
- 每个对象有一个隐藏的虚表指针(
vptr
),指向对应虚表; - 调用虚函数时,通过
vptr
查找虚表中函数地址,实现动态绑定。
二、多重继承下的问题
当一个类从 多个基类继承,而这些基类中都有虚函数时,派生类就需要为每个基类维护一套虚表和 vptr。
三、示例代码
#include <iostream>
using namespace std;
class A {
public:
virtual void fa() { cout << "A::fa()" << endl; }
};
class B {
public:
virtual void fb() { cout << "B::fb()" << endl; }
};
class C : public A, public B {
public:
void fa() override { cout << "C::fa()" << endl; }
void fb() override { cout << "C::fb()" << endl; }
};
四、对象内存布局图解(假设编译器实现方式)
C对象内存(简化)
+------------------+
| vptr_A |-----> vtable_A (for A)
+------------------+
| A 子对象成员变量 |
+------------------+
| vptr_B |-----> vtable_B (for B)
+------------------+
| B 子对象成员变量 |
+------------------+
| C 的成员变量 |
+------------------+
vtable_A:
+------------------+
| &C::fa() |
+------------------+
vtable_B:
+------------------+
| &C::fb() |
+------------------+
🔧说明:
-
C
类继承了A
和B
,它有两个虚函数表(分别对应 A 和 B); -
所以
C
对象中有两个vptr
:- 一个用于
A
的虚函数; - 一个用于
B
的虚函数;
- 一个用于
-
调用虚函数时,编译器根据当前指针(例如
A*
或B*
)决定使用哪个vptr
和哪张 vtable。
五、运行示例
int main() {
C obj;
A* pa = &obj;
B* pb = &obj;
pa->fa(); // 输出 C::fa(),通过 vptr_A 找到 vtable_A 中的 &C::fa
pb->fb(); // 输出 C::fb(),通过 vptr_B 找到 vtable_B 中的 &C::fb
return 0;
}
六、多重继承下的虚表指针调用流程
当你执行 pa->fa()
时:
- 编译器知道
pa
是A*
类型; - 假设
A
对象在C
中偏移为 0,则pa
本身就是vptr_A
的位置; - 跟着
vptr_A
指向vtable_A
; - 调用第一个槽(slot):
C::fa()
。
执行 pb->fb()
也是同理,只是偏移不一样。
七、相关注意事项
现象/问题 | 原因 |
---|---|
对象有多个 vptr | 因为每个虚基类都需要维护自己的虚函数表 |
强制转换指针可能出错 | 如 A* 转为 C* 需调整偏移 |
多态依然有效 | 编译器正确处理多个虚表指针 |
八、总结
项目 | 单继承 | 多重继承 |
---|---|---|
虚表数量 | 1 张 | 每个有虚函数的父类一张 |
虚表指针 | 1 个 vptr | 每个父类一个 vptr |
内存布局 | 简单 | 需要考虑偏移和指针调整 |
多态支持 | 简单调用 | 通过多个 vtable 实现 |