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!"
  1. 获取animal对象的vptr → 指向dog_vtable

  2. 从vtable中获取第一个函数指针 → &Dog_speak_impl

  3. 调用该函数 → 输出 "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对象时发生了什么?

构造过程:

  1. 分配内存:为Dog对象分配内存(包含Animal部分 + Dog部分)

  2. 调用Animal构造函数:

  • 设置vptr指向Animal的vtable
  • 初始化Animal的成员变量
  1. 调用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都不会改变,所以总是能调用到正确的函数。

posted @ 2025-09-01 23:03  灰灰奋斗录  阅读(12)  评论(0)    收藏  举报