钻石问题

在学习C++的时候,菱形继承问题绝对是一个不可避免的重点问题,那么什么是菱形继承问题呢?下图就是,长得像不像钻石?我画图确实很难看

示意图

因为C++允许多继承,当继承关系像上图这样子的时候,就会出现这样子的情况

A类是基类,B里面有个A我表示为B(A),C里面有个A我表示为C(A)

那么D里面有B和C我表示为D(B(A)C(A))

当我们想去使用D里面的A的时候,或者说访问A的部分值,在说白了究竟哪个A才是属于D的,D中的A究竟是B的A还是C的A?

class A 
{};
class B :public A
{};
class C :public A
{};
class D :public B, public C
{};

这么写可就错了,有的编译器甚至都不让你通过,直接给你报错

结构图

这很令人尴尬不是么,就算编译期让你通过了,也不要试图这样去通过D的对象访问其内部的A对象,这会让编译器很纠结

但是很简单给个vitual就好了

class A 
{};
class B :virtual public A
{};
class C :virtual public A
{};
class D : public B, public C
{};
结构图

他有了一个属于自己的A,调用A中的变量或者函数的时候就会去属于自己的A中调用,就不会让编译期纠结了

那么当我们想要通过D类对象访问D类对象内部的B中的A类部分呢?

因为继承的关系C的A和B的A应该是一样的(因为都在D类对象中的关系),访问到D中B的A部分之后,转而去调用d本身的A

让我们给每个类都加上一个成员变量,从内存的角度看这个问题

类结构改成A:_a B:_b C:_c D:_d,表示各个类独有的成员变量,虚继承关系不变

就会得到这样的结果

结构图

你们有看到虚表指针一类的东西么?没有虚表指针_vptr!

也就是说,内存布局是这样子的

示意图

我们查询一下d变量的内存地址,打开内存窗口看一下

内存日志

上图中最上面的两个红色区域就是d变量中B和C所占用的内存空间,每一行都是四个字节,因为我们已经知道没有被初始化的int变量的内存值用16进制表示就是cc cc cc cc所以在B和C中各自的第一行就是我们需要分别访问的A了,看着他们俩的A都不太对劲,不太像是一个对象的样子,我们把这两串神秘的值相减,结果为8

可以这么理解,01116bdc和01116be4之间的差值为8,就是说从B索引A的地址偏移比从C索引A的地址偏移要多上8位(在对象d中来说),从上图这个内存布局来看,对象d中B的位置和C的位置正好相差8个字节,就是说B中的A和C中的A存放的已经是一个地址了,这个地址会索引到真正使用的“A”就是上图中的绿色区域.

然而这并不是直接索引到绿色区域穿的偏移地址,其实一看你就会发现,这个地址过于大了,其实这个地址确实是偏移地址没错,但是指向的并不是直接的A的地址快,而是一个vbptr虚继承表,这个表和虚表可不是一个东西!!!!其中存放的是真正的偏移量(不是偏移地址了),这个偏移量是指从当前对象的地址开始算,往后偏移的字节数。

内存日志

vbptr虚基类指针会出现在类的一开始的内存地址(虽然你看到的是从int型开始的),然后这个内存地址加上我刚才所说的存储好的偏移量,就能索引了,而且有了这个表和指针之后,很大程度上减少了内存空间的占用

引自:https://www.cnblogs.com/lenomirei/p/5490714.html

你可能感兴趣的:(钻石问题)