虚函数表

虚函数表(vtable,Virtual Table)是 C++ 实现运行时多态的核心机制之一。虚函数表是编译器在处理带有虚函数的类时创建的一个数据结构,主要用于支持动态绑定,即根据对象的实际类型在运行时决定调用哪个函数。这是 C++ 中实现多态的关键。

虚函数表的工作原理

当一个类包含虚函数时,编译器会为该类创建一张虚函数表。虚函数表本质上是一个指针数组,每个元素都是指向该类虚函数的地址。具体来说:

  • 虚函数表包含指向该类虚函数的函数指针,当你调用某个虚函数时,程序会通过虚函数表来查找实际要调用的函数实现。

  • 每个类有一张虚函数表,类中的每个虚函数在虚函数表中都有一个对应的入口。

  • 对于每个包含虚函数的类的对象,编译器会在对象的内存布局中添加一个虚函数表指针(vptr),该指针指向对象所属类的虚函数表。

虚函数表的基本结构

假设有以下类层次结构:

#include <iostream>

class Base {
public:
    virtual void func1() {
        std::cout << "Base::func1" << std::endl;
    }
    virtual void func2() {
        std::cout << "Base::func2" << std::endl;
    }
};

class Derived : public Base {
public:
    void func1() override {
        std::cout << "Derived::func1" << std::endl;
    }
    void func2() override {
        std::cout << "Derived::func2" << std::endl;
    }
};

int main() {
    Base *b = new Derived();
    b->func1(); // 输出:Derived::func1
    b->func2(); // 输出:Derived::func2
    delete b;
    return 0;
}

在这个例子中,Base 类有两个虚函数 func1func2,派生类 Derived 重写了它们。

虚函数表的内部运作:

  1. 类的虚函数表(vtable)

    • Base 类的虚函数表存储了 Base::func1Base::func2 的地址。

    • Derived 类的虚函数表则存储了 Derived::func1Derived::func2 的地址,分别覆盖了 Base 类的对应函数。

    虚函数表的结构如下:

    Base 类的 vtable

    函数地址

    func1

    Base::func1

    func2

    Base::func2

    Derived 类的 vtable

    函数地址

    func1

    Derived::func1

    func2

    Derived::func2

  2. 对象的虚函数表指针(vptr)

    • 当创建一个 Derived 类的对象时,编译器会在该对象中添加一个指向 Derived 类虚函数表的指针 vptr

    • 通过 Base *b = new Derived();,指针 b 指向 Derived 对象,而这个对象的 vptr 指向的是 Derived 类的虚函数表,因此调用 func1()func2() 时会根据 vptr 指向的表找到 Derived::func1Derived::func2,并调用它们。

运行时多态的实现:

在调用 b->func1() 时:

  • 程序通过 bvptr 找到指向的 Derived 类的虚函数表。

  • 在虚函数表中查找 func1 的位置,得到 Derived::func1 的地址并进行调用。

  • 类似地,func2 也会调用 Derived::func2

虚函数表的特点

  1. 每个类有一张虚函数表

    • 每个包含虚函数的类会有自己的虚函数表。

    • 如果一个类没有虚函数,则没有虚函数表,也不会有虚函数表指针。

  2. 虚函数表指针(vptr)

    • 每个对象有一个虚函数表指针 vptr,该指针在对象创建时由构造函数初始化,指向对象所属类的虚函数表。

    • 对象在运行时通过 vptr 指针来查找虚函数的实现。

  3. 继承与虚函数表

    • 派生类会继承基类的虚函数表,并可以覆盖虚函数。如果派生类没有重写基类的虚函数,那么它会直接使用基类的虚函数地址。

    • 如果派生类重写了虚函数,则它会在自己的虚函数表中覆盖相应的条目。

  4. 性能开销

    • 虚函数调用比普通函数调用稍微慢一些,因为每次调用虚函数时,程序需要先通过 vptr 查找虚函数表,然后找到正确的函数指针进行调用。这是一种间接调用。

    • 然而,这种性能损失通常是微不足道的,只有在高性能场景下(如游戏开发、嵌入式系统等)可能会显得重要。

虚函数表的内存布局

一个类的虚函数表是静态存储的,所有对象共享这张表。一个包含虚函数的对象通常内存布局如下:

对象内存布局:
| vptr | object_data |

其中:

  • vptr 是一个指向类虚函数表的指针。

  • object_data 是对象本身的数据成员。

多继承和虚函数表

C++ 支持多继承,如果一个类继承了多个有虚函数的父类,那么该类可能会有多个虚函数表。例如,假设有两个基类 Base1Base2,它们各自都有虚函数,如果 Derived 类同时继承自 Base1Base2,那么 Derived 类的

Last updated