虚函数表(Virtual Function Table,简称vtable)是C++实现动态多态的核心机制,它是编译器在编译时为每个包含虚函数的类生成的一个函数指针数组。
虚函数表的结构和作用
基本原理:每个包含虚函数的类都有一个虚函数表,表中存储着该类所有虚函数的地址。每个对象实例都包含一个指向其类的虚函数表的指针(vptr)。
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
virtual ~Base() {}
};
class Derived : public Base {
public:
void func1() override { cout << "Derived::func1" << endl; }
virtual void func3() { cout << "Derived::func3" << endl; }
};
内存布局示例
Base类的vtable:
[0] -> Base::func1的地址
[1] -> Base::func2的地址
[2] -> Base::~Base的地址
Derived类的vtable:
[0] -> Derived::func1的地址 (重写了Base::func1)
[1] -> Base::func2的地址 (继承自Base)
[2] -> Derived::~Derived的地址
[3] -> Derived::func3的地址 (新增的虚函数)
对象内存布局:
Base obj: [vptr] -> Base的vtable
Derived obj: [vptr] -> Derived的vtable
动态绑定过程
当通过基类指针调用虚函数时:
Base* ptr = new Derived();
ptr->func1(); // 动态绑定过程如下:
- 通过对象的vptr找到对应的虚函数表
- 根据函数在vtable中的索引找到函数地址
- 调用该地址对应的函数
详细示例
#include <iostream>
using namespace std;
class Base {
public:
virtual void func1() {
cout << "Base::func1() called" << endl;
}
virtual void func2() {
cout << "Base::func2() called" << endl;
}
virtual ~Base() {
cout << "Base destructor" << endl;
}
// 辅助函数:显示vtable信息
void showVTableInfo() {
cout << "Base object address: " << this << endl;
cout << "vptr address: " << *(void**)this << endl;
}
};
class Derived : public Base {
public:
// 重写基类虚函数
void func1() override {
cout << "Derived::func1() called" << endl;
}
// 新增虚函数
virtual void func3() {
cout << "Derived::func3() called" << endl;
}
~Derived() override {
cout << "Derived destructor" << endl;
}
void showVTableInfo() {
cout << "Derived object address: " << this << endl;
cout << "vptr address: " << *(void**)this << endl;
}
};
class AnotherDerived : public Base {
public:
void func1() override {
cout << "AnotherDerived::func1() called" << endl;
}
void func2() override {
cout << "AnotherDerived::func2() called" << endl;
}
~AnotherDerived() override {
cout << "AnotherDerived destructor" << endl;
}
};
// 演示多态调用
void demonstratePolymorphism(Base* obj) {
cout << "\n=== 通过基类指针调用虚函数 ===" << endl;
obj->func1(); // 动态绑定到具体类型的func1
obj->func2(); // 动态绑定到具体类型的func2
}
int main() {
cout << "=== 虚函数表演示 ===" << endl;
// 创建不同类型的对象
Base base;
Derived derived;
AnotherDerived another;
cout << "\n--- 对象的vtable指针信息 ---" << endl;
base.showVTableInfo();
derived.showVTableInfo();
// 演示同一个类的不同对象共享相同的vtable
Derived derived2;
cout << "\n--- 相同类型对象的vtable ---" << endl;
cout << "derived1 vptr: " << *(void**)&derived << endl;
cout << "derived2 vptr: " << *(void**)&derived2 << endl;
cout << "两个Derived对象是否共享vtable: "
<< (*(void**)&derived == *(void**)&derived2 ? "是" : "否") << endl;
// 演示多态
cout << "\n=== 多态调用演示 ===" << endl;
Base* ptr1 = &base;
Base* ptr2 = &derived;
Base* ptr3 = &another;
demonstratePolymorphism(ptr1);
demonstratePolymorphism(ptr2);
demonstratePolymorphism(ptr3);
// 演示虚析构函数的重要性
cout << "\n=== 虚析构函数演示 ===" << endl;
Base* ptr = new Derived();
delete ptr; // 由于虚析构函数,会正确调用Derived的析构函数
return 0;
}
每个类一个表:同一个类的所有对象共享同一个虚函数表,但每个对象都有自己的vptr指向该表。
继承关系:派生类的虚函数表包含基类的虚函数,重写的函数会替换基类版本,新增的虚函数会追加到表末尾。
内存开销:每个对象增加一个指针的开销(通常8字节),每个类增加一个函数指针数组。
性能影响:虚函数调用比普通函数调用多一次间接寻址,但现代CPU的分支预测可以很好地优化这个过程。
实现细节
// 编译器大致按以下方式实现虚函数调用:
// ptr->func1() 被编译为类似:
// (*(ptr->vptr[0]))(ptr)
// 其中vptr[0]是func1在vtable中的索引位置
注意事项
构造函数中的虚函数:在构造函数中调用虚函数时,调用的是当前正在构造的类的版本,而不是最终派生类的版本。
虚函数表的初始化:对象的vptr在构造函数执行前就已经初始化,指向正确的虚函数表。
多重继承的复杂性:在多重继承情况下,一个对象可能包含多个vptr,每个基类一个。
虚函数表是C++实现面向对象特性的核心技术,理解它的工作原理有助于写出更高效的代码,也有助于理解C++对象模型的底层实现。