虚函数表的设计和多态的实现
虚表指针
类直接定义虚函数:编译器自动在对象头部插入 vptr。
继承含虚函数的父类:子类复用父类的 vptr,不会创建新的vptr
单继承(子类继承一个含虚函数的父类)
1.创建新的虚函数表
2.沿用父类的虚表指针(vptr)指向新虚表(vtable)
3.重写父类虚函数,覆盖虚表中原函数指针
4.新增虚函数,往虚表中加函数指针
5.vptr 通常位于对象的头 4/8 字节
class Base {
public:virtual void foo() {} // vptr 指向 Base 的虚表int a;
};class Derived : public Base {
public:virtual void foo() override {} // 重写,虚表更新virtual void bar() {} // 新增虚函数,追加到虚表int b;
};[ vptr ] --> 指向 Derived 的虚表(含 foo 和 bar)
[ a ] // Base::a
[ b ] // Derived::b
多继承(继承多个含虚函数的父类)
1.创建新的虚函数表,有几个父类就新建几个
2.沿用父类的虚表指针指向新虚表
3.重写父类虚函数,覆盖对应虚表中原函数指针
4.新增虚函数,往第一张虚表中加函数指针
class Base1 {
public:virtual void foo1() {} // Base1 有 vptrint b1;
};class Base2 {
public:virtual void foo2() {} // Base2 有 vptrint b2;
};class Derived : public Base1, public Base2 {
public:virtual void foo1() override {} // 重写 Base1::foo1virtual void bar() {} // 新增虚函数,追加到 Base1 的虚表int d;
};[ Base1::vptr ] --> 指向 Derived 为 Base1 新建的虚表(含 foo1 和 bar)
[ Base1::b1 ] // Base1 的成员
[ Base2::vptr ] --> 指向 Derived 为 Base2 新建的虚表(仅含 foo2)
[ Base2::b2 ] // Base2 的成员
[ Derived::d ] // 子类新增成员
一些问题
1.虚函数表需要对象的指针才可以找到,所以静态成员函数,友元函数玩不了
2.析构函数设为虚函数,在delete父类指针时,通过去虚表中查找可以正确调用子类析构,避免内存泄漏
3.内联函数和虚函数是冲突的,内联函数会将函数的指令展开在程序中,而虚函数是通过虚表去查找调用
多态的实现
1.引用的底层是指针
-
当用 父类引用/指针 指向子类对象时,本质上是 用父类类型的指针访问子类对象。
2.父类指针会指向特定位置
-
如果是 单继承,父类指针直接指向子类对象的起始地址(和子类指针相同)。
-
如果是 多重继承,父类指针可能会 调整偏移量,指向子类对象中对应父类子对象的起始位置。
3.调用虚函数就是用父类指针取头4/8字节,得到虚表指针,去查虚表调用对应虚函数