C++虚函数与类对象模型深度解析
目录
1. 引言
2. 单继承下的虚函数表
2.1 基本概念
2.2 示例分析
3. 多重继承下的虚函数表
3.1 基本概念
3.2 示例分析
4. 虚函数表指针(vptr)的存储
4.1 单继承
4.2 多重继承
5. 常见面试题解析
问题1:D 继承 B1 和 B2,D 新增虚函数放在哪里?
问题2:D 有几个虚表指针?
问题3:如果 B1 没有虚函数,B2 有虚函数
6. 总结
1. 引言
在C++中,虚函数是实现运行时多态(动态绑定)的核心机制,而虚函数表(vtable)和虚表指针(vptr)是实现这一机制的关键。理解虚函数在类对象模型中的存储方式,对于深入掌握C++面向对象编程至关重要。本文将详细分析:
- 单继承下的虚函数表布局
- 多重继承下的虚函数表布局
- 虚函数表指针(vptr)的存储方式
- 新增虚函数对虚表的影响
2. 单继承下的虚函数表
2.1 基本概念
当一个类包含虚函数时,编译器会为该类生成一个虚函数表(vtable),存储所有虚函数的地址。每个对象的内存布局中,前4字节(32位系统)或前8字节(64位系统)存储指向虚函数表的指针(vptr
)。
2.2 示例分析
class A {
public:virtual void func1() { cout << "A::func1" << endl; }virtual void func2() { cout << "A::func2" << endl; }
};class B : public A {
public:virtual void func1() override { cout << "B::func1" << endl; } // 重写virtual void func3() { cout << "B::func3" << endl; } // 新增
};
内存布局:
对象 | 虚表指针(vptr) | 虚表内容 |
---|---|---|
A | vptr_A | A::func1 , A::func2 |
B | vptr_B | B::func1 (重写), A::func2 , B::func3 (新增) |
关键点:
B
继承A
,因此B
的虚表包含A
的所有虚函数(func1
被重写,func2
保留)。B
新增的func3
附加到虚表末尾。
3. 多重继承下的虚函数表
3.1 基本概念
在多重继承中,派生类会为每个包含虚函数的基类维护一个独立的虚函数表。如果派生类新增虚函数,它们会附加到第一个基类的虚表末尾。
3.2 示例分析
class B1 {
public:virtual void f1() { cout << "B1::f1" << endl; }
};class B2 {
public:virtual void f2() { cout << "B2::f2" << endl; }
};class D : public B1, public B2 {
public:virtual void f1() override { cout << "D::f1" << endl; } // 重写 B1::f1virtual void f2() override { cout << "D::f2" << endl; } // 重写 B2::f2virtual void f3() { cout << "D::f3" << endl; } // 新增虚函数
};
内存布局:
对象 | 虚表指针(vptr) | 虚表内容 |
---|---|---|
D | vptr_B1 | D::f1 , D::f3 (新增) |
vptr_B2 | D::f2 |
关键点:
D
继承B1
和B2
,因此有 2 个虚表指针(vptr_B1
和vptr_B2
)。D
新增的f3
附加到B1
的虚表末尾(因为B1
是第一个基类)。B2
的虚表仅存储D
重写的f2
。
4. 虚函数表指针(vptr)的存储
4.1 单继承
- 只有一个
vptr
,位于对象起始地址。 - 示例:
A a;
B b;
cout << *(void**)&a; // 输出 A 的虚表地址
cout << *(void**)&b; // 输出 B 的虚表地址
4.2 多重继承
- 每个基类对应一个
vptr
,按继承顺序排列。 - 示例:
D d;
void** vptr1 = *(void***)&d; // B1 的 vptr
void** vptr2 = *(void***)((char*)&d + sizeof(B1)); // B2 的 vptr
5. 常见面试题解析
问题1:D
继承 B1
和 B2
,D
新增虚函数放在哪里?
✅ 答案:放在第一个基类 B1
的虚表末尾。
问题2:D
有几个虚表指针?
✅ 答案:2 个(对应 B1
和 B2
)。
问题3:如果 B1
没有虚函数,B2
有虚函数
6. 总结
继承方式 | 虚表指针数量 | 新增虚函数存储位置 |
---|---|---|
单继承 | 1 | 附加到基类虚表末尾 |
多重继承 | 等于基类数量 | 附加到第一个基类虚表末尾 |
关键结论:
- 虚函数表(vtable)是实现动态绑定的核心。
- 单继承时,派生类虚表包含基类虚函数 + 新增虚函数。
- 多重继承时,派生类为每个基类维护独立虚表,新增虚函数放在第一个基类虚表末尾。