【C++】复杂的多继承及其缺陷(菱形继承)

本篇要分享的内容是C++中多继承的缺陷:菱形继承。

以下为本篇目录

目录

1.多继承的缺陷与解决方法

2.虚继承的底层原理

3.虚继承底层原理的设计原因


1.多继承的缺陷与解决方法

首先观察下面的图片判断它是否为多继承

这实际上是一个单继承,单继承的特点是一个子类只有一个直接继承的父类,即使又多层继承关系,但是只有一个直接父类,都称作单继承 

多继承的图示如下

 可以看到多继承中的子类扮演了两个角色,就相当于桃花既能开出好看的桃花,也能结果。

所以多继承的特点是一个子类有两个或以上的直接父类时称这个关系叫做多继承。

那在上图中使用多继承是没有错误的,他可以在一个类中结合多个类的特点,多继承的本身并没有错误,但是出错的往往是在一些使用场景下会有缺陷,如下图

有了多继承可能就会导致菱形继承(如上图)。

可以看到Student类和Teacher类都会继承Person中的属性,

但是此时Assistant同时又继承了Student类和Teacher类的话,Person中的属性在Assistant中就会出现两次,会有二义性。

这也是为什么java语言中没多继承用法的原因。

观察如下代码

#include<iostream>
using namespace std;
class Person
{
public:
	string _name; // 姓名
};

class Student :  public Person
{
protected:
	int _num; //学号
};

class Teacher : public Person
{
protected:
	int _id; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

 可以看到在报错中它有规定使用的访问限定符,也就是说它会将同样的属性及信息再继承一份,也就是同时具有两份数据信息从而导致数据冗余占用空间,

如果Person类的空间很大,那么浪费的空间会更大。

那如何解决这样的问题呢?

首先我们可以使用访问限定符解决二义性

,这样使用没有问题

其次是在出现菱形继承的玩儿法之后C++祖师爷又更新了一个关键字:virtual(虚拟)

我们只需要在被多继承的类的继承方法前加上virtual,即可使用虚继承,

#include<iostream>
using namespace std;
class Person
{
public:
	string _name; // 姓名
};

class Student : virtual public Person
{
protected:
	int _num; //学号
};

class Teacher : virtual public Person
{
protected:
	int _id; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

使用了虚继承之后就可以调用冗余数据的属性了

 

 可以看到,使用了虚继承之后不管是Student类中的_name还是Person类中的_name都不管用了,使用a直接可以调用_name,并且所用的空间也是同一份地址空间。

这样就解决了在菱形继承中数据冗余的问题。

但是在一个庞大的项目中这样的问题语法依旧会坑害不少人,所以尽量的能少用多继承,就要少用多继承。

2.虚继承的底层原理

既然要了解菱形继承的底层原理,我们不妨设计一个简单一点的代码便于观察,代码如下

class A
{
public:
	int _a;
};
class B : public A
//class B : virtual public A
{
public:
	int _b;
};
class C : public A
//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._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

那上面的菱形继承关系也很简单,如下图

除了观察菱形继承外,main函数中的内容对菱形继承的测试也同样重要;

我们将代码调试,并观察内存窗口。

使用D创建了对象d,在内存中观察d的地址即可。

 可以看到的是在B类存放了两个两个值,1和3

C类中也存放了两个值,2和4

D类中存放了一个值2,他与对象d中修改_d的值相同;

那这样存放数据是什么意思呢?

上面的代码没有使用虚函数,所以存放了两个值,导致了数据的二义性;

接下来我们使用虚函数,虚函数可以解决数据冗余和二义性的问题,我们继续观察内存的变化

class A
{
public:
	int _a;
};
//class B : public A
class B : virtual public A
{
public:
	int _b;
};
//class C : public A
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._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

使用了虚函数继续观察内存模块

和上面对比我们发现,在B类中存放了两行数据:第一行为一个地址,第二行为所修改的数据;

在C类中,存放的数据与B类相似

D和A中的数据被修改为最后所修改的数据;

可以看到在B类和C类中将地址取代了第一次所存的数据,从而达到解决数据二义性的目的;

那这个存放的地址又是什么意思是呢?

 我们继续再调出一个监视内存的窗口来观察

再第二个观察内存的窗口中输入B类的地址,这时你就会发现在第二个内存表中存在一个数,这个数子就是距离最终修改a的偏移量,上图为十六进制的14

当我们将B类中的第一行存放的地址加上十六进制的14,就会得到_a最终的值,_a=0;

我们再来举出一组例子来证明不是巧合

 可以看到_a只被赋值,而B中不仅存放了_b的值,同样也存放了一个指针,指向了距离A的偏移量,也同样是将B类中第一行的地址加上指针所指的偏移量(8),就是1所在的位置。

以上就是设计的原理,虽然设计很多内存和地址的关系,但是这就是虚函数底层的实现设计。

3.虚继承底层原理的设计原因

那为什么要这么设计呢?

如以下情况

一个B类创建的指针会指向bb对象,也有可能指向d对象,

所以我们无法得知这个指针所指向的对象,就只能靠指针来检查另一块内存上所存放的偏移量,通过计算偏移量来计算虚继承中二义性的变量。

以上就是菱形继承的设计缺陷以及后序的设计的解决思路,以及解决思路的底层设计。

其实多继承本身没有问题,只是菱形继承的用法让多继承成为了大坑。

即使本人水平有限,尽管不遗余力但本篇对虚继承的探索仍有不足,还请读者指正,感谢您的阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/125310.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

clang插件对llvm源码插桩,分析函数调用日志(2)--google镜像

tick_plot__compile.ipynb clang插件对llvm源码插桩&#xff0c;分析函数调用日志(1) 分析 进出、链、出 df进出df[ df[tickKind].isin( [FuncEnter,FuncReturn] ) ]#代码中&#xff0c;只有在函数进入时&#xff0c;计算了链条长度 并写磁盘 df入df[ df[tickKind].isin…

18 CDN详解

1、理解CDN 1.CDN 和电商系统的分布式仓储系统一样&#xff0c;就近发货给客户(客户端)&#xff0c;所以&#xff0c;必然是提前在仓库中存储了某些商品. 2.CDN最擅长的是缓存静态数据&#xff0c;比如电商系统的热点静态页面&#xff0c;秒杀场景的页面等.问题&#xff1a;向…

tqdm学习

from tqdm import tqdmepochs 10 epoch_bar tqdm(range(epochs)) count 0 for _ in epoch_bar:count count1print("count {}".format(count))print(_)每次就是一个epoch

磁盘空间占用巨大的meta.db-wal文件缓存(tracker-miner-fs索引服务)彻底清除办法

磁盘命令参考本博客linux磁盘空间满了怎么办. 问题: 磁盘空间被盗 今天瞄了一下我的Ubuntu系统盘&#xff0c; nftdiggernftdigger-Ubuntu:~$ df -h 文件系统 容量 已用 可用 已用% 挂载点 udev 16G 0 16G 0% /dev tmpfs 3.2G 1.9…

【今日文章】:如何用css 实现星空效果

【今日文章】&#xff1a;如何用css 实现星空效果 需求实现tips: 需求 用CSS 实现星空效果的需求&#xff1a; 屏幕上有“星星”&#xff0c;且向上移动。移动的时候&#xff0c;动画效果要连贯&#xff0c;不能出现闪一下的样子。 实现 这里我们需要知道&#xff0c;“星星”是…

简单剖析程序的翻译过程!

本文旨在讲解一段源程序如何翻译成机器所能识别的二进制的命令的&#xff0c;希望通过本文&#xff0c;能使读者对一段程序的翻译过程有进一步的认识&#xff01; 这里首先要介绍的是一段程序从编写完成到执行需要经过以下几个步骤&#xff01; 1.预处理 首先讲到的是预处理&…

UI设计软件有哪些好用和免费的吗?

在我们分享五个有用的原型工具之前&#xff0c;完成原型&#xff0c;将优化界面&#xff0c;这次是UI设计师的任务&#xff0c;UI设计软件对设计师非常重要&#xff0c;UI设计工具是否使用直接影响最终结果&#xff0c;然后有人会问&#xff1a;UI界面设计使用什么软件&#xf…

IP-guard WebServer RCE漏洞复现

0x01 产品简介 IP-guard是由溢信科技股份有限公司开发的一款终端安全管理软件&#xff0c;旨在帮助企业保护终端设备安全、数据安全、管理网络使用和简化IT系统管理。 0x02 漏洞概述 漏洞成因 在Web应用程序的实现中&#xff0c;参数的处理和验证是确保应用安全的关键环节…

大数据毕业设计选题推荐-设备环境监测平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

基于工业智能网关的汽车充电桩安全监测方案

近年来&#xff0c;我国新能源汽车产业得到快速发展&#xff0c;电动车产量和销量都在持续增长&#xff0c;不仅国内市场竞争激烈&#xff0c;而且也远销海外&#xff0c;成为新的经济增长点。但与此同时&#xff0c;充电设施的运营却面临着安全和效率的双重挑战。 当前的充电桩…

node插件MongoDB(四)—— 库mongoose 的文档操作使用

文章目录 前言&#xff08;1&#xff09;问题&#xff1a;安装的mongoose 库版本不应该过高导致的问题&#xff08;2&#xff09;重新安装低版本 一、插入文档1. 代码2. node终端效果3. 使用mongo.exe查询数据库的内容 二、删除文档1. 删除一条2. 批量删除3. 代码 前言 &#…

Python--列表及其应用场景

1.为什么需要列表 思考&#xff1a;有一个人的姓名(laowang)怎么书写存储程序&#xff1f; 用 变量。如&#xff1a;name laowang 但是&#xff0c;如果要记录很多人的名字&#xff0c;怎么办&#xff1f; 思考&#xff1a; 如果一个班级100位学生&#xff0c;每个人的…

Vue.js 学习总结(3)—— vite 打包图片时报错 Rollup failed to resolve import...

问题 图片依赖&#xff1a; Vite 打包前端项目时图片无法引入&#xff0c;报如下错误&#xff1a; ERROR [vite]: Rollup failed to resolve import "%7BlibeiDanmuKongmu%7D" from "D:/java/workspace/jeecgboot-vue3/src/views/funeral/tombInfo/area.vue?…

ros自定义消息包无法编译生成.h文件的问题解决

ros自定义消息包无法编译生成.h文件的问题解决 想要创建一个ROS功能包专门存放自己自定义的消息&#xff0c;想将这些消息都生成.h&#xff0c;可以由别的功能包来调用。 但是参照网上的诸多帖子未能解决&#xff0c;例如 https://blog.csdn.net/feidaji/article/details/10360…

yolov5 通过视频进行目标检测

打开yolov5-master文件夹&#xff0c;可以看到一个名为data的文件夹&#xff0c;在data中创建一个新的文件夹&#xff0c;命名为videos。 打开yolov5-master中的detect.py可以看到一行代码&#xff08;大概在245行左右&#xff09;为 parser.add_argument(--source, typestr,…

逐次变分模态分解(Sequential Variational Mode Decomposition,SVMD)(附代码)

代码原理 逐次变分模态分解&#xff08;Sequential Variational Mode Decomposition&#xff0c;SVMD&#xff09;是一种用于信号处理和数据分析的方法。它可以将复杂的信号分解为一系列模态函数&#xff0c;每个模态函数代表了信号中的一个特定频率成分。SVMD的主要目标是提取…

ZYNQ_project:key_breath

[Synth 8-327] inferring latch for variable led_breath_reg ["C:/Users/warrior/Desktop/ZYNQ/pl/key_breath/rtl/led_breath.v":66] 因为在组合逻辑中&#xff0c;用了非阻塞赋值的方式赋值信号。 组合逻辑自己给自己赋值会产生组合回环&#xff0c;输出不稳定。 …

Android 11.0 禁止弹出系统simlock的锁卡弹窗功能实现

1.前言 在11.0的系统rom产品定制化开发中,在关于定制sim卡定制机的一款产品中,需要实现simlock锁卡功能,在系统实现锁卡功能以后,在开机的过程中,或者是在插入sim卡 后,当系统检测到是禁用的sim卡后,就会弹出simlock锁卡弹窗,要求输入puk 解锁密码,功能需求禁用这个弹…

狮子鱼社区团购小程序V18.9全开源独立版+小程序前端 安装教程

狮子鱼社区团购商城系统小程序V18.9独立开源版&#xff0c;该系统一直开源本身也非常完善&#xff0c;此系统拿来即用非常方便&#xff0c;同上一版一样播播资源特别优化很多细节首页美化了下&#xff0c;如小程序端授权窗口美化了下&#xff0c;该版本用户授权接口正常。功能测…

Linux本地部署1Panel现代化运维管理面板并实现公网访问

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…
最新文章