继承(五)

在研究之前,先来回忆一下类/对象大小的计算

  • 类大小计算遵循前面学过的结构体对齐原则。
  • 类的大小与数据成员有关与成员函数无关。
  • 类的大小与静态数据成员无关。
  • 虚继承对类的大小的影响
  • 虚函数对类的大小的影响

前面三点在之前已经介绍过了,这节主要是研究第四点,而第五点会在未来进行探讨,在探讨虚继承对内存模型的影响时,需要了解一下“虚基类表”:

  • virtual base table
    本类地址与虚基类表指针地址的差。
    虚基类地址与虚基类表指针地址的差。
  • virtual base table pointer(vbptr)

 下面先依照上面的类图的结构来构建数据模型,来细细探讨:

#include <iostream>
using namespace std;

class BB {
public:
    int bb_;
};

class B1 : virtual public BB {
public:
    int b1_;
};

class B2 : virtual public BB {
public:
    int b2_;
};

class DD : public B1, public B2 {
public:
    int dd_;
};

int main(void) {

    return 0;
}

这里数据成员都是整型,就不会受结构体对齐的影响,比较容易算出它的大小。下面分别来打印一下各个类的大小:

编译运行:

可见由于有了虚继承,则并非按我们的预想来计算类大小了,下面来推导一下这种情况的内存模型B1和DD,首先来推导一下B1类的内存模型:

编译运行:

打印出地址之后,下面来进行推导:

根据vbtl的存放规则来计算一下它里面的值是多少?

这样B1的内存模型就推导出来了,口说无评,下面用代码主要来论证上面画的内存模型中的vbtl存放的是对的:

编译运行:

刚好论证了之前推断的模型是正确的,所以可以看到B1类的大小为12个字节,因为第一个字节是空出来存放虚基类表指针的。

用这种方法再来推断一下DD类的内存模型:

编译运行:

打印地址之后下面来推导:

那先来计算填充一下虚基类表中的值:

下面用代码来验证一下:

编译运行:

论证了结果是正确的,目前还未学习到虚函数,如果有了它内存模型会更加复杂,因为虚函数会有虚表指针,它指向虚表,这个在之后再来探讨。

想一下为什么有了虚继承内存模型会变得如此复杂?

先从"virtual"这个单词的意思来理解:它是存在的、共享的、间接的,拿DD这个派生类来说,虚基类BB是存在的;对DD数据成员来说是共享的,BB不会创建两份;对于DD对象要访问BB数据成员需要间接访问,其间接访问就体现在虚基类表指针了,如下:

对于DD来说,如果要找到BB,需要通过B1或B2的虚基类表的偏移位置最终找到BB:

说到这,抛出一个问题:dd对象要访问bb这个数据成员,是直接访问还是间接访问呢?

实际上如果是对象访问的话还是直接访问,因为内存模型在编译时刻已经决定了,但是,如果通过指针来访问情况就不一样了:

为什么说是间接访问呢?DEBUG看下地址便知:

关于这节比较绕,需细细体会~

原文地址:https://www.cnblogs.com/webor2006/p/5621825.html