C++动态绑定(运行时多态)的实现机制
C++的动态绑定(运行时多态)是通过虚函数表(vtable) 和虚函数表指针(vptr) 来实现的。
1. 虚函数表(vtable)
-
每个包含虚函数的类都有一个对应的虚函数表
-
虚函数表是一个函数指针数组,存储该类所有虚函数的地址
-
虚函数表在编译时生成,存储在程序的只读数据段
2. 虚函数表指针(vptr)
-
每个包含虚函数的类的对象都有一个隐藏的vptr成员
-
vptr指向该对象所属类的虚函数表
-
vptr在对象构造时被设置
实现机制详解
内存布局示例:
class Animal {
public:
virtual void speak() const { cout << "Animal sound" << endl; }
virtual void eat() const { cout << "Eating" << endl; }
int age;
};
class Dog : public Animal {
public:
void speak() const override { cout << "Woof!" << endl; }
void eat() const override { cout << "Eating dog food" << endl; }
string breed;
};
内存中的实际情况:
// 编译器为Animal生成的代码
void Animal_speak_impl(const Animal* this) {
cout << "Animal sound" << endl;
}
void Animal_eat_impl(const Animal* this) {
cout << "Eating" << endl;
}
// 编译器为Dog生成的代码
void Dog_speak_impl(const Animal* this) {
// 注意:参数类型仍然是Animal*,但实际是Dog*
cout << "Woof!" << endl;
}
void Dog_eat_impl(const Animal* this) {
cout << "Eating dog food" << endl;
}
// 虚函数表
VTable animal_vtable = { &Animal_speak_impl, &Animal_eat_impl };
VTable dog_vtable = { &Dog_speak_impl, &Dog_eat_impl }; // 这里指向Dog的实现!
内存布局示意图:
Animal对象:
+----------------+
| vptr | --> 指向Animal的vtable
| age |
+----------------+
Animal的vtable:
+----------------+
| &Animal::speak | ← 指向Animal::speak的实现
| &Animal::eat | ← 指向Animal::eat的实现
+----------------+
Dog对象:
+----------------+
| vptr | --> 指向Dog的vtable
| age |
| breed |
+----------------+
Dog的vtable:
+----------------+
| &Dog::speak | ← 指向Dog::speak的实现(重写了基类!)
| &Dog::eat | ← 指向Dog::eat的实现(重写了基类!)
+----------------+
调用过程:
当你有:
Animal* animal = new Dog();
animal->speak(); // 应该输出 "Woof!"
-
获取animal对象的vptr → 指向dog_vtable
-
从vtable中获取第一个函数指针 → &Dog_speak_impl
-
调用该函数 → 输出 "Woof!"
完整的对象和vtable关系:
Dog对象在内存中:
+----------------+ +-------------------+
| vptr | ---> | &Dog_speak_impl | ← 指向Dog的实现
| Animal数据成员 | | &Dog_eat_impl | ← 指向Dog的实现
| Dog特有成员 | +-------------------+
+----------------+ (Dog的vtable)
Animal对象在内存中:
+----------------+ +-----------------------+
| vptr | ---> | &Animal_speak_impl | ← 指向Animal的实现
| Animal数据成员 | | &Animal_eat_impl | ← 指向Animal的实现
+----------------+ +-----------------------+
(Animal的vtable)
为什么获取animal对象的vptr → 指向dog_vtable,而不是animal_vtable
关键机制:vptr在对象构造时被设置
对象构造的顺序和vptr的设置
Dog dog; // 创建Dog对象时发生了什么?
构造过程:
-
分配内存:为Dog对象分配内存(包含Animal部分 + Dog部分)
-
调用Animal构造函数:
- 设置vptr指向Animal的vtable
- 初始化Animal的成员变量
- 调用Dog构造函数:
- 重要:重新设置vptr指向Dog的vtable
- 初始化Dog特有的成员变量
详细的内存变化过程
步骤1: 内存分配后
Dog对象:
+----------------+
| 未初始化的vptr |
| 未初始化的数据 |
| 未初始化的数据 |
+----------------+
步骤2: Animal构造函数执行后
Dog对象:
+----------------+ +-----------------------+
| vptr | ---> | &Animal::speak |
| Animal数据 | | &Animal::eat |
| 未初始化的数据 | +-----------------------+
+----------------+ (Animal的vtable)
步骤3: Dog构造函数执行后(关键步骤!)
Dog对象:
+----------------+ +-------------------+
| vptr | ---> | &Dog::speak | ← vptr被重新指向Dog的vtable!
| Animal数据 | | &Dog::eat |
| Dog特有数据 | +-------------------+
+----------------+ (Dog的vtable)
简单来说:Dog对象在构造完成后,它的vptr就固定指向Dog的vtable了。无论你用Animal还是Dog来指向这个对象,内存中的vptr都不会改变,所以总是能调用到正确的函数。