参考文章 虚函数表概述

多态的实现细节

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数

#include<iostream>
using std::cout;
​
class Base
{
public:
    Base() {
        cout << "this is Base Create Func\n";
    }
    virtual ~Base() {
        cout << "~Base()\n";
    }
    virtual void virtual_func() {
        // 获取 vptr 的值
        cout << "Base this: " << this << ", vptr: " << *(void**)this << "\n";
    }
};
​
class A final : public Base
{
public:
    void virtual_func() override {
        // 获取 vptr 的值
        cout << "A this: " << this << ", vptr: " << *(void**)this << "\n";
    }
};
​
class B final : public Base
{
public:
    void virtual_func() override {
        // 获取 vptr 的值
        cout << "B this: " << this << ", vptr: " << *(void**)this << "\n";
    }
};
​
int main() {
    auto base = new Base();
    Base* a = new A();
    Base* a1 = new A();
    Base* b = new B();
​
    base->virtual_func();
    a->virtual_func();
    a1->virtual_func();
    b->virtual_func();
​
    return 0;
}
// 运行结果
this is Base Create Func
this is Base Create Func
this is Base Create Func
this is Base Create Func
    
//           对象的地址             指向的虚函数表的地址       
Base this: 00000185A07B64D0, vptr: 00007FF62700BC30 // base
    
A    this: 00000185A07B6750, vptr: 00007FF62700BCC0 // a
A    this: 00000185A07B5DA0, vptr: 00007FF62700BCC0 // a1 同一张虚函数表
    
B    this: 00000185A07B62A0, vptr: 00007FF62700BD00 // b

从代码的运行结果可以看到,同一个类的不同对象共用同一张虚函数表,这是在编译期间就决定好的

内存布局

虚表指针(vptr)的位置

本身就是一个指针 大小就是指针的大小

  • 虚表指针(vptr)是一个隐藏的成员,通常存储在对象的内存中,一般位于对象的起始位置(具体位置由编译器决定)。

  • 通过 this 指针可以访问到虚表指针,但 this 本身并不是 vptr 的地址。

虚函数指针内存图.png


v table

虚函数表是指在每个包含虚函数的类中都存在着一个函数地址的数组。当我们用父类的指针来操作一个子类的时候,这张虚函数表指明了实际所应该调用的函数。

C++的编译器保证虚函数表的指针存在于对象实例中最前面的位置,这样通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

虚函表1.png


单继承的情况下内存布局

没重写的情况:

虚函表2.png

  • 虚函数按照其声明顺序放于表中。

  • 父类的虚函数在子类的虚函数前面


派生类覆盖基类虚函数

虚函表3.png

  • 虚表中派生类覆盖的虚函数的地址被放在了基类相应的函数原来的位置

  • 派生类没有覆盖的虚函数延用基类的


多继承下的虚函数表

基类和派生类概述:

  • A、B 和 C 是虚函数类,每个类都会有一个虚表指针(vptr)和一个对应的虚表(vtable)

  • D 是从 A、B 和 C 多继承的派生类,且覆盖了每个基类的虚函数,并新增了一个虚函数 funcD()

class A {
public:
    virtual void funcA() {}
    int a_member;
};
​
class B {
public:
    virtual void funcB() {}
    int b_member;
};
​
class C {
public:
    virtual void funcC() {}
    int c_member;
};
​
class D : public A, public B, public C {
public:
    void funcA() override {} // 重写 A 的虚函数
    void funcB() override {} // 重写 B 的虚函数
    void funcC() override {} // 重写 C 的虚函数
    virtual void funcD() {}  // D 自己的虚函数
    int d_member;
};
​

虚函表4.png

  • 每个基类拥有自己的子对象,独立的虚表指针(vptr)用来维护从该基类继承的虚函数行为。

  • 多继承时,基类之间是“平等的”,并不是所有虚函数都由第一个基类的虚表管理。

如果重写了基类的虚函数:

虚函表5.png


钻石型\菱形继承问题

菱形继承.png

解决方法:

菱形继承2.png

采用虚继承的方式:

class A {
public:
    int data;
};
​
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
​
  • BC 中没有直接包含 A 的子对象,而是通过 虚基表 指向 A 的唯一子对象。

  • A 的子对象被存储在 D 中,由 BC 共享