当前位置:首页 » 《休闲阅读》 » 正文

C++:面向对象大坑:菱形继承

16 人参与  2024年05月04日 12:36  分类 : 《休闲阅读》  评论

点击全文阅读


菱形继承

1.单继承1.概念 2.多继承2.1概念2.2菱形继承1.概念2.问题3.样例理解二义性数据冗余对于内存模型抽象化 2.3菱形虚拟继承(解决菱形继承的问题)1.概念2.样例理解对于内存模型抽象化 2.4总结 3.问题总结1.C++有多继承,为什么?为什么Java没有?2.多继承的问题是什么?3.菱形继承的问题是什么,如何解决?4.底层角度如何解决菱形继承的问题(数据冗余和二义性)?

1.单继承

1.概念

单继承:一个子类只有一个直接父类时称这个继承关系为单继承。

图示:
在这里插入图片描述

2.多继承

2.1概念

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。

图示:
在这里插入图片描述

2.2菱形继承

1.概念

菱形继承:菱形继承是多继承的一种特殊情况。即:一个类是另外几个类的子类,而这几个子类又是另外一个类的父类。

基本模型:

在这里插入图片描述

2.问题

但是呢,菱形继承却有一些问题:它会造成数据的冗余以及数据的二义性。比如下面,在Assistant的对象中Person成员会有两份。

在这里插入图片描述

3.样例理解

注:以下在VS2022 X64环境下验证。

class A{public:int _a;};class B : public A{public:int _b;};class C : public A{public:int _c;};class D : public B, public C{public:int _d;};int main(){D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;}
二义性

如果我们直接访问d中的_a,编译器不知道要访问继承B的 _a还是继承C的 _a,会有歧义。

在这里插入图片描述
在这里插入图片描述

数据冗余

首先观察调试窗口:我们可以看到创建的d变量的地址。

在这里插入图片描述
然后通过内存窗口,我们可以看到d中存储了2个_a,相同的部分就会重复存储。
在这里插入图片描述

对于内存模型抽象化

在这里插入图片描述

2.3菱形虚拟继承(解决菱形继承的问题)

1.概念

菱形虚拟继承就是在菱形继承的腰部继承时(即父类第一次有多个子类时)加上关键字virtual即可。

2.样例理解

其他部分不变,我们对于上述代码进行菱形虚拟继承,并且加上一句直接访问的代码(d._a = 100),再次进行测试。

class A{public:int _a;};class B : virtual public A{public:int _b;};class C : virtual public A{public:int _c;};class D : public B, public C{public:int _d;};int main(){D d;d.B::_a = 1;d.C::_a = 2;    d._a = 100; //新增的一句代码d._b = 3;d._c = 4;d._d = 5;return 0;}

首先观察调试窗口:我们可以看到创建的d变量的地址。
在这里插入图片描述

此时再让代码执行d.B::_a = 1这句,通过内存窗口可以看到其变化,但是和菱形继承的变化位置不同。
在这里插入图片描述
再让代码执行d.C::_a = 2这句,通过内存窗口可以看到其是直接在原来的位置处改变的,只有一份 _a。
在这里插入图片描述
再让代码执行d._a = 100这句,内存窗口变化如下:
在这里插入图片描述
再执行d._b = 3这条语句。
在这里插入图片描述
再执行d._c = 4这条语句。
在这里插入图片描述

再执行d._d = 5这条语句。
在这里插入图片描述
有个疑问,此处圈住的部分是什么呢?它看起来像一个地址(X64环境下),那么其是存储什么的呢?

解释一下,其确实为两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。
在这里插入图片描述

通过内存窗口可以观察出:继承的B中存储了十六进制下的28,即十进制下的40。对比下图,和偏移量相等。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于内存模型抽象化

在这里插入图片描述

2.4总结

1.通过虚拟继承,D类对象中只有一个A类的_a,从而解决了数据冗余和二义性。
2.菱形虚拟继承的对象模型:
每个继承对象中存储一个虚基表,虚基类(即上例中的A)放在最下面,成为公共部分。
在这里插入图片描述
3.存储的地址的作用
其为虚基表指针,指向虚基表,虚基表中存储偏移量,可以找到下面存储的A。从而方便切片的场景。
4.菱形虚拟继承也会改变中间类的结构,让它们的结构和D的结构类似。
这样是为了防止以下的场景:

D d;B b;B& ref = d;ref = b;

如果不存储成类似的结构,那么找到B类存储的_a就比较困难。

3.问题总结

1.C++有多继承,为什么?为什么Java没有?

这个得从C++历史发展来看,在C++发展史中,在完善面向对象的过程中,祖师爷考虑到了现实生活中确实有一部分东西可以继承多个类,比如西红柿既是水果,又是蔬菜,因此C++有了多继承。而Java在这方面通过C++的痛苦因此做出了 改变,只允许单继承。

2.多继承的问题是什么?

多继承本身没有问题,但是有多继承就会有菱形继承,而菱形继承就有许多问题。

3.菱形继承的问题是什么,如何解决?

数据冗余以及二义性。通过菱形虚拟继承解决。

4.底层角度如何解决菱形继承的问题(数据冗余和二义性)?

菱形虚拟继承在底层改变了数据的存储结构,将虚基类存储在了最下面,作为公共部分,让多继承而来的父类的数据共享,共用一份,而在继承的那部分中则存储了虚表指针,指向虚基表,从而得到其中存储的偏移量,进而可以实现切片时数据的完整性。


点击全文阅读


本文链接:http://www.zhangshiyu.com/post/103512.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1