菱形继承问题(钻石问题)


我看到网上有很多人都在说虚继承和虚表的关系,我一直很郁闷,虚继承和虚表没有什么太大的关系,虚表是当有虚函数出现的时候才会有的,光是使用虚继承是不会有虚表的!当然也就不会有虚表指针!!从我后面的截图可以看到使用虚继承对象的内存构造中并没有出现虚表指针!跟虚继承有关的是一个虚基类表(vbptr),这个表在调试的监视窗口是看不见的,但有了虚继承之后使用sizeof可以明显看出,类型所占内存的大小扩大了4个字节!

况且问题就不一样好么?虚继承解决的是二义性的问题,而虚函数是为了解决多态的问题。

 

仔细看过之后就会发现虚继承表和虚函数表不是一个东西,地址不一样存放的东西也不一样,上图中虚函数表地址是0x008c7b40虚继承表地址是0x008c7b48,虚函数表存放虚函数指针,虚继承表存放的是整型偏移量

 

在学习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?

1 class A 
2 {};
3 class B :public A
4 {};
5 class C :public A
6 {};
7 class D :public B, public C
8 {};

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

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

但是很简单给个vitual就好了

1 class A 
2 {};
3 class B :virtual public A
4 {};
5 class C :virtual public A
6 {};
7 class D : public B, public C
8 {};

他有了一个属于自己的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型开始的),然后这个内存地址加上我刚才所说的存储好的偏移量,就能索引了,而且有了这个表和指针之后,很大程度上减少了内存空间的占用

本文永久更新链接地址

相关内容

    暂无相关文章