C++ 多重继承下的内存布局

1. 多重继承

多重继承示例代码如下:

class Base1 {
public:
    void f0() {}
    virtual void f1() {}
    int a;
};

class Base2 {
public:
    virtual void f2() {}
    int b;
};

class Derived : public Base1, public Base2 {
public:
    void d() {}
    void f2() {}  // override Base2::f2()
    int c;
};

int main() {
    Base2 *b2 = new Base2;
    Derived *d = new Derived;
}

class Derived对象有两个vptr,那么有没有可能将这俩vptr合并成一个呢?

答案是不行。这是因为与单继承不同,在多继承中,class Base1class Base2相互独立,它们的虚函数没有顺序关系,即f1和f2有着相同对虚表起始位置的偏移量,所以不可以按照偏移量的顺序排布;并且class Base1class Base2中的成员变量也是无关的,因此基类间也不具有包含关系;这使得class Base1class Base2class Derived中必须要处于两个不相交的区域中,同时需要有两个虚指针分别对它们虚函数表索引

 2. top offset

在上节Derived的虚函数表中,有两个top offset,其值分别为0和-16,那么这个offset起什么作用呢?

在此,先给出结论:将对象从当前这个类型转换为该对象的实际类型的地址偏移量

仍然以前面的class Derived为例,其虚函数表布局如下:

Vtable for Derived
Derived::_ZTV7Derived: 7u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI7Derived)
16    (int (*)(...))Base1::f1
24    (int (*)(...))Derived::f2
32    (int (*)(...))-16
40    (int (*)(...))(& _ZTI7Derived)
48    (int (*)(...))Derived::_ZThn16_N7Derived2f2Ev

为了能方便理解本节内容,我们不妨将Derived虚函数表认为是 class Base1和class Base2两个类的虚函数表拼接而成 。因为是多重继承,所以编译器将先继承的那个认为是 主基类(primary base) ,因此Derived类的主基类就是class Base1。

在多继承中,当最左边的类中没有虚函数时候,编译器会将第一个有虚函数的基类移到对象的开头,这样对象的开头总是有vptr

首先看虚函数表的前半部分,如下:

0     (int (*)(...))0
8     (int (*)(...))(& _ZTI7Derived)
16    (int (*)(...))Base1::f1
24    (int (*)(...))Derived::f2

正是因为编译器将class Base1作为Derived的主基类,并将自己的函数加入其中。从上述可以看出offset为0,也就是说Base1类的指针不需要偏移就可以直接访问Derived::f2()

接着看虚函数表的下半部分:

32    (int (*)(...))-16
40    (int (*)(...))(& _ZTI7Derived)
48    (int (*)(...))Derived::_ZThn16_N7Derived2f2Ev

偏移值为-16,因为是多重继承,所以class Base1和class Base2类型的指针或者引用都可以指向class Derived对象,那么又是如何调用正确的成员函数呢?

Base2* b2 = new Derived;
b2->f2(); //最终调用Derived::f2();

由于不同的基类起点可能处于不同的位置,因此当需要将它们转化为实际类型时,this指针的偏移量也不相同,且由于多态的特性,b2的实际类型在编译时期是无法确定的;那必然需要一个东西帮助我们在运行时期确定b2的实际类型,这个东西就是offset_to_top。通过让this指针加上offset_to_top的偏移量,就可以让this指针指向实际类型的起始地址

 

posted @   小熊酱  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示