虚函数表的结构
- 基本结构:每个包含虚函数的类都有一个虚函数表(vtable)。虚函数表是一个指针数组,数组中的每个元素是一个指向虚函数的指针。当类对象被创建时,对象的内存布局中会有一个隐藏的指针(vptr),它指向该类的虚函数表。
- 单继承场景:在单继承情况下,如果子类没有重写父类的虚函数,那么子类的虚函数表会直接继承父类虚函数表的内容。如果子类重写了父类的某个虚函数,那么虚函数表中对应位置的指针会被替换为指向子类重写后的虚函数。
虚函数表的工作原理
- 运行时多态实现:当通过基类指针或引用调用虚函数时,程序首先根据对象的vptr找到对应的虚函数表,然后根据虚函数在表中的索引找到实际要调用的函数地址,从而实现运行时多态。例如:
class Base {
public:
virtual void func() { std::cout << "Base::func" << std::endl; }
};
class Derived : public Base {
public:
void func() override { std::cout << "Derived::func" << std::endl; }
};
int main() {
Base* ptr = new Derived();
ptr->func(); // 通过vptr找到Derived的虚函数表,调用Derived::func
delete ptr;
return 0;
}
多重继承场景下虚函数表的变化
- 多个虚函数表:在多重继承中,一个子类可能会从多个父类继承虚函数表。例如,若
class Derived : public Base1, public Base2
,且Base1
和Base2
都有虚函数,那么Derived
可能会有多个虚函数表(具体取决于编译器实现,但通常至少有两个)。
- 布局变化:每个虚函数表对应一个父类继承体系,子类重写的虚函数会在对应的虚函数表中更新指针。例如,如果
Derived
重写了Base1
的某个虚函数,那么Derived
对应Base1
继承体系的虚函数表中该虚函数的指针会被更新。
虚继承场景下虚函数表的变化
- 共享虚函数表:虚继承用于解决菱形继承中的重复基类问题。在虚继承中,所有从虚基类继承的子类共享同一个虚基类子对象,并且可能共享同一个虚函数表(取决于编译器实现)。
- 偏移调整:为了确保能够正确访问虚基类的成员和虚函数,编译器可能会在虚函数表中添加额外的信息,如偏移量,用于在运行时计算虚基类子对象的地址。
对多态性实现的影响
- 多重继承:多重继承增加了虚函数表的复杂性,但仍然能够实现运行时多态。通过不同的虚函数表指针,可以正确调用不同父类继承体系中的虚函数。
- 虚继承:虚继承通过共享虚基类子对象和可能共享的虚函数表,在保证多态性的同时,解决了菱形继承中的重复数据问题。额外的偏移信息确保了在复杂继承体系下能够正确定位虚基类的虚函数,从而实现多态性。