虚函数的复杂(继承)内存布局

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录


继承关系一般分为 单继承多继承而多继承的出现就又导致了一种特殊的继承关系 菱形继承为了解决菱形继承的冗余和二义性又有了 菱形虚拟继承的解决方案。本篇主要介绍以上四种继承关系的 类对象虚函数内存布局
tips:本篇中的例程和内存布局均采用编译器visual studio 2022。

单继承无虚函数覆盖

假设有如下所示的继承关系

class Base{
public:
    int ibase;
    Base():ibase(10){}
    virtual void a(){ cout << "Base::a()" << endl; }
    virtual void b(){ cout << "Base::b()" << endl; }
};
class Derive : public Base{
public:
    int iderive;
    Derive():iderive(100){}
    virtual void c(){ cout << "Derive::c()" << endl; }
};

在这个继承关系中派生类并没有重写基类的任何函数。那么在派生类的的实例中其内存布局如下

image-20230203221658797

可以看到以下几点结论

  1. 派生类继承了基类的虚函数表
  2. 派生类的虚函数进入了虚函数表并且排在基类的虚函数后面。

单继承有虚函数覆盖

假设有如下所示的继承关系

class Base{
public:
    int ibase;
    Base():ibase(10){}
    virtual void a(){ cout << "Base::a()" << endl; }
    virtual void b(){ cout << "Base::b()" << endl; }
};
class Derive : public Base{
public:
    int iderive;
    Derive():iderive(100){}
    virtual void a(){ cout << "Derive::a()" << endl; }
    virtual void c(){ cout << "Derive::c()" << endl; }
};

在这个继承关系中派生类中有重写了基类的部分函数。那么在派生类的的实例中其内存布局如下

image-20230203221726390

可以看到以下几点结论

  1. 重写后的虚函数放在了虚函数表中基类对应虚函数原来的位置
  2. 没有被重写的虚函数在在虚函数表中不变。

多继承无虚函数覆盖

假设有如下所示的继承关系

class Base1{
public:
    int ibase1;
    Base1():ibase1(11){}
    virtual void a(){ cout << "Base1::a()" << endl; }
    virtual void b(){ cout << "Base1::b()" << endl; }
};
class Base2{
public:
    int ibase2;
    Base2():ibase2(22){}
    virtual void a(){ cout << "Base2::a()" << endl; }
    virtual void b(){ cout << "Base2::b()" << endl; }
};
class Derive : public Base1, public Base2{
public:
    int iderive;
    Derive():iderive(100){}
    virtual void c(){ cout << "Derive::c()" << endl; }
};

在这个继承关系中派生类中有重写了基类的部分函数。那么在派生类的的实例中其内存布局如下

image-20230203221737292

可以看到以下几点结论

  1. 派生类继承了两个基类的两个虚函数表
  2. 派生类的两个虚函数表指针分别在自己的区域内指向自己的虚函数表
  3. 派生类继承的两个基类按继承顺序在内存中排布。

多继承有虚函数覆盖

假设有如下所示的继承关系

class Base1{
public:
    int ibase1;
    Base1():ibase1(11){}
    virtual void a(){ cout << "Base1::a()" << endl; }
    virtual void b(){ cout << "Base1::b()" << endl; }
};
class Base2{
public:
    int ibase2;
    Base2():ibase2(22){}
    virtual void a(){ cout << "Base2::a()" << endl; }
    virtual void b(){ cout << "Base2::b()" << endl; }
};
class Derive : public Base1, public Base2{
public:
    int iderive;
    Derive():iderive(100){}
    virtual void a(){ cout << "Derive::a()" << endl; }
    virtual void c(){ cout << "Derive::c()" << endl; }
};

在这个继承关系中派生类中有重写了基类的部分函数。那么在派生类的的实例中其内存布局如下

image-20230203221752676

可以看到以下结论

​ 派生类重写的虚函数对所有基类都生效。

菱形继承有虚函数覆盖

假设有如下所示的继承关系

class Parent{
public:
    int iparent;
    Parent():iparent(10){}
    virtual void a() { cout << "Parent::a()" << endl; }
    virtual void b() { cout << "Parent::b()" << endl; }
    virtual void c() { cout << "Parent::c()" << endl; }
};
class Child1 : public Parent{
public:
    int ichild1;
    Child1():ichild1(111){}
    virtual void a() { cout << "Child1::a()" << endl; }
    virtual void b_child1() { cout << "Child1::b_child1()" << endl; }
    virtual void c_child1() { cout << "Child1::c_child1()" << endl; }
};
class Child2 : public Parent{
public:
    int ichild2;
    Child2():ichild2(222){}
    virtual void a() { cout << "Child2::a()" << endl; }
    virtual void b_child2() { cout << "Child2::b_child2()" << endl; }
    virtual void c_child2() { cout << "Child2::c_child2()" << endl; }
};
class GrandChild : public Child1, public Child2{
public:
    int igrandchild;
    GrandChild():igrandchild(1000){}
    virtual void a() { cout << "GrandChild::a()" << endl; }
    virtual void b_child1() { cout << "GrandChild::b_child1()" << endl; }
    virtual void b_child2() { cout << "GrandChild::b_child2()" << endl; }
    virtual void c_grandchild() { cout << "GrandChild::c_grandchild()" << endl; }
};

在此继承关系中两个中间类分别重写了父类的a函数孙子类又重写了爷爷类的a函数、分别重写了两个父类的b_child2函数和b_child1函数。

image-20230203221803692

可以看到以下结论

  1. 菱形继承导致数据冗余Parent::iparent = 10;
  2. 当访问Parent::iparent时会产生二义性需要手动添加Child1::或者Child2::;
  3. 派生类的虚函数自动排往第一个虚函数表结尾。

菱形虚拟继承有虚函数覆盖

为了解决菱形继承产生的数据冗余和二义性所以C++引入了虚基类的概念也就是进行虚拟继承只需在继承的时候再添加一个关键字virtual

如下所示继承关系和上个例子一样仅仅只是添加了关键字virtual变成了虚拟继承。

class Parent{ ...... };
class Child1 : virtual public Parent{ ...... };
class Child2 : virtual public Parent{ ...... };
class GrandChild : public Child1, public Child2{ ...... };

image-20230203221814894

可以看到以下结论

  1. 本来冗余的数据Parent::iparent=10不再冗余Parent整体部分搬迁到最末尾
  2. 虚函数表由两个变为三个中间类的两个虚函数表中都去掉重复的部分即继承下来的Parent的虚函数
  3. Parent的虚函数由Parent的虚函数表指针单独维护
  4. 派生类的虚函数仍自动排往第一个虚函数表
  5. 额外多了两个指针__vbptr该指针指向一个整形数组元素都为偏移值
  6. 根据__vbTable中的偏移值即可找到Parent实例的位置。
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6