菱形继承、虚拟继承

在这里插入图片描述

虚拟继承所解决的问题:

在这里插入图片描述
多继承中有一种特殊的结构,菱形继承,会引发二义性、数据冗余的问题,因此C++引出了虚拟继承以解决这个问题,具体操作上是在继承权限前面加上一个virtual前缀

class A
{
public:
	A(int aa = 1)
		:_aa(aa)
	{}
	int _aa;
};
class B : virtual public A
{
public:
	B(int bb = 2)
		:_bb(bb)
	{}
	int _bb;
};
class C : virtual public A
{
public:
	C(int cc = 3)
		:_cc(cc)
	{}
	int _cc;
};
class D : public B, public C
{
public:
	D(int dd = 4)
		:_dd(dd)
	{}
	int _dd;
};

原理分析

让原来多继承存储父类(A)变量的地方变成存储一个地址,这个地址指向了一张虚基表,这个虚基表中存储了一个偏移量,这个偏移量是指向虚基表的指针和新开辟的一块存储父类(A)变量的空间的差,编译器能够通过这个偏移量找到新开辟的存储父类(A)变量的空间,这段话比较难以理解,我下面用图来解释说明:

在这里插入图片描述
我用上面的代码创建了d对象,在内存窗口中查看&d的地址,注意到我们使用的是小端机器,也就是低地址存低字节序,高地址存高字节序,我这里,0x00e96f24 0x00e96f64是两个指针,指向了就是上面所说的虚基表,这里的0x00000023 0x0000002d 0x00000032,注意是16进制,我们这里转换成10进制,35和45和50,也就是_bb和_cc,所以这里也就能看出B、C、D的组成,注意到这后面这个0x00000028,其实就是新开辟的存储父类(A)变量的空间

接下来,我们查看0x00e96f24 0x00e96f64这两个地址,即虚基表
在这里插入图片描述
在这里插入图片描述
一个存的是0x00000014 另一个存的是0x0000000c,转换为10进制:20 12,我们再回去看看D结构体
在这里插入图片描述
对于0x00e96f24,加上20字节,就指向了新开辟的存储父类变量(A)的空间,0x00e96f64同理

这里可能有人会注意到为什么20和12没有放在虚基表的首地址处,这是由于虚基表还有其他作用,这里我们就不赘述了

我们这里如果创建B C类型的变量,其实也会发现各自原来存父类(A)地址也都会创建一张各自的虚基表,以存储A中的偏移量,其实也就是说,只要一个类是被virtual修饰,其自身,以及子类,都会存在虚基表

问题

1、为什么在虚基表中存偏移量,而不是在原位置直接存偏移量?
因为父类很可能有多个成员,如果存在原地,那么每一个对象,都会去存储这个偏移量,效率低,
2、那么此时可能会有人追问,如果有父类多个成员变量,存在虚基表中,那依旧每个父类成员都需要一个各自的偏移量?
这个提问就有问题,我们的虚基表存的不是每个父类成员变量的偏移量,而是新开辟的存储父类变量的空间的首地址,前面这个例子是因为父类只有一个成员变量,所以看上去,就是指向成员变量的,编译器能够通过这一个偏移量,访问到所有的成员变量
3、为什么我测试sizeof D的时候发现,有了virtual sizeof D还变大了?
我们来算一算,D没有virtual的大小,B来的有B自身的b,4字节,B继承A的4字节,总共8字节;C也同理,8字节,也就是D从B、C继承了16字节,D自身的4字节,总共20字节,然后考虑对齐问题,最后确定为20字节。D有virtual时,B来的有B自身的b,4字节,B指向虚基表的指针,4字节,总共8字节;C也同理,那么D从B C继承来了16字节,以及自己的4字节,另外还给父类成员变量开辟了4字节,总共24字节,考虑对齐问题,确定为24字节。确实有virtual比没virtual大。
在这里插入图片描述
在这里插入图片描述
但是这是针对这个例子,A类往往不只有一个成员变量,下例,A模拟有大量的成员变量,我们直接用int _a[100]数组来表示
在这里插入图片描述
在这里插入图片描述
明显的发现,有virtual会小很多,我们再算算账:没有virtual的情况,B来的有B自身的4字节,B继承A来的400字节(100个元素,每个元素的类型是int),B总共404字节,考虑对齐问题,最后确定为404字节,C同理,也就是说D类型从B C类型各自继承了404字节,总共808字节,加上D自身的4字节,我们这里会发现,父类A中相同的内容,从B来了400字节,从C来了400字节,那么就会有大量的重复内容,所以占了很大的空间,而对于有virtual,B和C对于A的成员变量都是存的虚基表地址,也就4字节,然后另开辟一块空间去存储父类A的成员变量,也就是A成员变量只存了一份,那么我们算一算,BC各来了8字节,D自身4字节,另开辟了400字节,总共420字节,就能够很大程度的节约空间
4、菱形继承的形式除了上面的BC继承A,D继承BC这种,还有什么别的形式?
在这里插入图片描述
D就会涉及到二义性的问题,D中有从B来的A,也有从C2来的A,我们就需要给B和C1或者B和C2加上virtual以防止二义性,数据冗余的问题
后面这个图,D直接继承了A,也通过B继承了A,那么此时D存储了两份A成员变量,也就是菱形继承的模样,当然也就需要给B D加上virtual,可以不用深入研究这里,因为菱形继承一般都是在笔试、面试、考研中遇到,但对于实际工作敲代码,很少会搞菱形继承这种结构来坑害自己,并且其实不同编译器上,对于菱形继承、菱形虚拟继承的实现,都有小小的差异,我上面也大多是都是验证,得到的答案,以后在新的环境中,也可以同样的方式去思考总结,得到该环境对于菱形继承、菱形虚拟继承的处理方式。
5、是不是只要是菱形继承,就需要加virtual去防止二义性,数据冗余?
不是,根据需求而言,是会有需求想让同一个变量存储两份,存不同的结果,当然,大多数需求都是只需要一份,需要去加个virtual虚拟一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失去梦想的小草

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值