# C++ 虚函数与动态绑定

## 多态与动态绑定
为了实现 C++ 的多态，C++ 使用了动态绑定技术，该技术的核心是**虚函数表**（简称虚表）。

## 类的虚函数表
每个包含了虚函数的类都包含一个虚表，一个子类如果继承了包含虚函数的父类，那么这个类也拥有自己的虚表，例如
```cpp
class A {
public:
    virtual void vfunc1();
    virtual void vfunc2();
    void func1();
    void func2();
private:
    int data1_, data2_;
};
class B : public A {
public:
    void vfunc1() override;
    void func1();
};
class C : public B {
public:
    void vfunc2() override;
    void func2();
private:
    int data1_, data2_;
};
```

A 包含虚函数 `vfunc1()` ，B 继承自 A，A 的虚表如图所示
![UMqKwRv6nlpISct](https://pic-upyun.zwyyy456.tech/smms/2023-12-26-065815.jpg)

虚表是一个指针数组，其元素是虚函数的指针，数组中的每个元素对应一个虚函数的指针。普通的函数（即非虚函数），其调用并不需要经过虚表，所以虚表的元素并不包括普通函数的函数指针。

虚函数指针的赋值发生在编译器的编译阶段，也就是在编译阶段，虚表就被构建出来了。

## 虚表指针
虚表是属于类的（有点像静态成员变量），而不属于某个具体的对象，一个类只需要一个虚表即可，同一个类的所有对象都使用同一个虚表。

为了指定对象的虚表，对象内部包含一个虚表的指针，来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针，编译器在类中添加了一个指针，`*__vptr`，用来指向虚表。这样，当类的对象在创建时便拥有了这个指针，且这个指针的值会自动被设置为指向类的虚表。

一个子类的父类如果包含虚函数，那么这个子类也拥有自己的虚表，所以这个子类的对象也包含一个虚表指针，用来指向它的虚表。
![2IiUQ9NMtksgCpv](https://pic-upyun.zwyyy456.tech/smms/2023-12-26-065816.jpg)

类 A 包括两个虚函数，故 A 的虚函数表包含两个指针，分别指向 `A::vfunc1()`和 `A::vfunc2()`。

类 B 继承于类 A，故类 B 可以调用类 A 的函数，但由于类 B 重写了 `B::vfunc1()` 函数，故 B 的虚函数表的两个指针分别指向 `B::vfunc1()` 和 `A::vfunc2()`。

类 C 继承于类 B，故类 C 可以调用类 B 的函数，但由于类 C 重写了 `C::vfunc2()` 函数，故C 的虚表的两个指针分别指向`B::vfunc1()`（指向继承的最近的一个类的函数）和 `C::vfunc2()`。

对象的虚表指针用来指向自己所属类的虚表，虚表中的指针会指向**其继承的最近的一个类的虚函数**。

## 动态绑定
C++ 的动态绑定即是通过**虚表**和**虚表指针**来实现的。

承接上面的代码，假设我们实例化子类 B 的对象`bObj`，并声明基类 A 的指针 `p`，然后让 `p` 指向 `bObj`，即 `A *p = &bObj;`，那么，当我们使用 `p` 来调用 `vfunc1()` 时，由于 **__vptr** 也是基类的一部分，因此 `p->__vptr` 的值实际上是 `&bOjb->__ptr`，即 `p->__vptr` 指向了类 B 的虚函数表，因此 `p->vfunc1()` 实际上调用了 `B::vfunc1()`。

因此，我们只要注意**虚表指针**的值，即可弄清楚到底调用了哪个类的函数。

我们把虚表调用虚函数的过程称为动态绑定，其表现出来的现象称为运行时多态，传统的函数调用我们称为静态绑定（函数调用在编译阶段就能确定下来了）。

执行函数的动态绑定需要符合以下三个条件：
- 通过指针调用函数；
- 指针发生从子类向父类的转换，例如 `A *p = &bObj;`；
- 调用的是虚函数；

## 参考资料
- 《Cpp Primer》第五版
- [C++ 虚函数表剖析](https://zhuanlan.zhihu.com/p/75172640)


