虚函数表
虚函数表(vtable,Virtual Table)是 C++ 实现运行时多态的核心机制之一。虚函数表是编译器在处理带有虚函数的类时创建的一个数据结构,主要用于支持动态绑定,即根据对象的实际类型在运行时决定调用哪个函数。这是 C++ 中实现多态的关键。
虚函数表的工作原理
当一个类包含虚函数时,编译器会为该类创建一张虚函数表。虚函数表本质上是一个指针数组,每个元素都是指向该类虚函数的地址。具体来说:
虚函数表包含指向该类虚函数的函数指针,当你调用某个虚函数时,程序会通过虚函数表来查找实际要调用的函数实现。
每个类有一张虚函数表,类中的每个虚函数在虚函数表中都有一个对应的入口。
对于每个包含虚函数的类的对象,编译器会在对象的内存布局中添加一个虚函数表指针(vptr),该指针指向对象所属类的虚函数表。
虚函数表的基本结构
假设有以下类层次结构:
在这个例子中,Base
类有两个虚函数 func1
和 func2
,派生类 Derived
重写了它们。
虚函数表的内部运作:
类的虚函数表(vtable):
Base
类的虚函数表存储了Base::func1
和Base::func2
的地址。Derived
类的虚函数表则存储了Derived::func1
和Derived::func2
的地址,分别覆盖了Base
类的对应函数。
虚函数表的结构如下:
Base 类的 vtable
函数地址
func1
Base::func1
func2
Base::func2
Derived 类的 vtable
函数地址
func1
Derived::func1
func2
Derived::func2
对象的虚函数表指针(vptr):
当创建一个
Derived
类的对象时,编译器会在该对象中添加一个指向Derived
类虚函数表的指针vptr
。通过
Base *b = new Derived();
,指针b
指向Derived
对象,而这个对象的vptr
指向的是Derived
类的虚函数表,因此调用func1()
和func2()
时会根据vptr
指向的表找到Derived::func1
和Derived::func2
,并调用它们。
运行时多态的实现:
在调用 b->func1()
时:
程序通过
b
的vptr
找到指向的Derived
类的虚函数表。在虚函数表中查找
func1
的位置,得到Derived::func1
的地址并进行调用。类似地,
func2
也会调用Derived::func2
。
虚函数表的特点
每个类有一张虚函数表:
每个包含虚函数的类会有自己的虚函数表。
如果一个类没有虚函数,则没有虚函数表,也不会有虚函数表指针。
虚函数表指针(vptr):
每个对象有一个虚函数表指针
vptr
,该指针在对象创建时由构造函数初始化,指向对象所属类的虚函数表。对象在运行时通过
vptr
指针来查找虚函数的实现。
继承与虚函数表:
派生类会继承基类的虚函数表,并可以覆盖虚函数。如果派生类没有重写基类的虚函数,那么它会直接使用基类的虚函数地址。
如果派生类重写了虚函数,则它会在自己的虚函数表中覆盖相应的条目。
性能开销:
虚函数调用比普通函数调用稍微慢一些,因为每次调用虚函数时,程序需要先通过
vptr
查找虚函数表,然后找到正确的函数指针进行调用。这是一种间接调用。然而,这种性能损失通常是微不足道的,只有在高性能场景下(如游戏开发、嵌入式系统等)可能会显得重要。
虚函数表的内存布局
一个类的虚函数表是静态存储的,所有对象共享这张表。一个包含虚函数的对象通常内存布局如下:
其中:
vptr
是一个指向类虚函数表的指针。object_data
是对象本身的数据成员。
多继承和虚函数表
C++ 支持多继承,如果一个类继承了多个有虚函数的父类,那么该类可能会有多个虚函数表。例如,假设有两个基类 Base1
和 Base2
,它们各自都有虚函数,如果 Derived
类同时继承自 Base1
和 Base2
,那么 Derived
类的
Last updated