【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对象,
所以我们无法得知这个指针所指向的对象,就只能靠指针来检查另一块内存上所存放的偏移量,通过计算偏移量来计算虚继承中二义性的变量。
以上就是菱形继承的设计缺陷以及后序的设计的解决思路,以及解决思路的底层设计。
其实多继承本身没有问题,只是菱形继承的用法让多继承成为了大坑。
即使本人水平有限,尽管不遗余力但本篇对虚继承的探索仍有不足,还请读者指正,感谢您的阅读。
相关文章:
【C++】复杂的多继承及其缺陷(菱形继承)
本篇要分享的内容是C中多继承的缺陷:菱形继承。 以下为本篇目录 目录 1.多继承的缺陷与解决方法 2.虚继承的底层原理 3.虚继承底层原理的设计原因 1.多继承的缺陷与解决方法 首先观察下面的图片判断它是否为多继承 这实际上是一个单继承,单继承的特…...
esp32-rust-no_std-examples-blinky
什么是裸机环境? 裸机环境是指没有可供使用的操作系统环境。当编译的 Rust 程序拥有 no_std 属性时,该程序无权访问上述 std 章节中提到的某些特定功能。尽管仍支持使用配网或引入复杂数据结构等功能,但实现方式将会更加复杂。 no_std…...
GitHub上的开源工业软件
github上看到一个中国人做的流体力学开源介绍,太牛了! https://github.com/clatterrr/FluidSimulationTutorialsUnity 先分析一下工业仿真软件赛道 工业仿真软件的赛道和产品主要功能如下: 1. 工艺仿真赛道: - 工厂布局优化&am…...
Centos7安装配置中文输入法
Centos7安装配置中文输入法 在安装CentOS时,我们为了方便使用,语言选择了中文,但是我们发现,在Linux命令行或者是浏览器中输入时,我们只能输入英文,无法输入汉字。 来,跟随脚步,设…...
【OJ比赛日历】快周末了,不来一场比赛吗? #11.11-11.17 #12场
CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…)比赛。本账号会推送最新的比赛消息,欢迎关注! 以下信息仅供参考,以比赛官网为准 目录 2023-11-11(周六) #5场比赛2023-11-12…...
提取当前文件夹下多文件夹中的数据
提取当前文件夹下多文件夹中的数据 1.实现步骤 现在D:\临时\图库 这个文件夹下有多个文件夹,现在需要将多个文件夹中的文件全部移动到D:\临时\图库下; $sourcePath "D:\临时\图库" $targetPath "D:\临时\图库"Get-ChildItem -Path $sourcePath -File …...
深度学习(生成式模型)——Classifier Free Guidance Diffusion
文章目录 前言推导流程训练流程测试流程 前言 在上一节中,我们总结了Classifier Guidance Diffusion,其有两个弊端,一是需要额外训练一个分类头,引入了额外的训练开销。二是要噪声图像通常难以分类,分类头通常难以学习…...
C语言 每日一题 11.9 day15
数组元素循环右移问题 一个数组A中存有N( > 0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥0)个位置,即将A中的数据由(A0A1⋯AN−1)变换为&…...
STM32F103C8T6第三天:pwm、sg90、超声波、距离感应按键开盖震动开盖蜂鸣器
1. 定时器介绍1(317.21) 软件定时(之前的定时方法)(软件延时)缺点:不精确、占用CPU资源 void Delay500ms() //11.0592MHz {unsigned char i, j, k;_nop_();i 4;j 129;k 119;do{do{while (-…...
栈的顺序存储实现(C语言)(数据结构与算法)
栈的顺序存储实现通常使用数组来完成。实现方法包括定义一个固定大小的数组,以及一个指向栈顶的指针。当元素入栈时,指针加一并将元素存储在相应位置;当元素出栈时,指针减一并返回相应位置的元素。 1. 顺序栈定义 #define MaxSi…...
设计模式 -- 观察者模式
说明 author blog.jellyfishmix.com / JellyfishMIX - githubLICENSE GPL-2.0 定义 观察者模式(Observer Design Pattern) 也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的: Define a one-to-many depe…...
Go RabbitMQ简介 使用
RabbitMQ简介 RabbitMQ 是一个广泛使用的开源消息队列系统,它实现了高级消息队列协议(AMQP)标准,为分布式应用程序提供了强大的消息传递功能。RabbitMQ 是 Erlang 语言编写的,具有高度的可扩展性和可靠性,…...
【面经】Spring框架中用了哪些设计模式
在Spring框架中,主要运用了以下几种设计模式: 工厂模式: Spring beanFactory使用工厂模式在应用程序中管理对象的创建。 通过使用工厂模式,Spring可以将对象的创建与使用分离,降低耦合度。 单例模式: Spr…...
SpringBoot自动配置的原理篇,剖析自动配置原理;实现自定义启动类!附有代码及截图详细讲解
SpringBoot 自动配置 Condition Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操作 思考:SpringBoot是如何知道要创建哪个Bean的?比如SpringBoot是如何知道要创建RedisTemplate的?…...
苹果Ios系统app应用程序开发者如何获取IPA文件签名证书时需要注意什么?
今天呢想和大家介绍介绍苹果App开发者如何获取IPA文件签名证书的步骤和注意事项。对于苹果应用程序开发者而言,获取IPA文件签名证书是发布应用程序至App Store的重要步骤之一。签名证书能够确保应用程序的安全性和可信度,并使其能够在设备上正确运行。 …...
算法通关村第七关-黄金挑战二叉树迭代遍历
大家好我是苏麟 , 今天带来二叉树的迭代遍历 . 二叉树的迭代遍历 前序编列 描述 : 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 题目 : LeetCode 二叉树的前序遍历 : 144. 二叉树的前序遍历 分析 : 前序遍历是中左右,如果还有左子树就一…...
2023-11-Rust
学习方案:Rust程序设计指南 1、变量和可变性 声明变量:let 变量、const 常量 rust 默认变量一旦声明,就不可变(immutable)。当想改变 加 mut(mutable) 。 const 不允许用mut ,只能声明常量,…...
iOS代码混淆----自动
先大致解释一下“编译"、"反编译": 编译:就是把千千万万行字符串(也叫代码,或者源文件),变成010101010101(机器码,也叫目标代码) 编译过程:预处理-编译-汇编-链接 我的脚本运行在预处理阶段。 反编…...
对Mysql和应用微服务做TPS压力测试
1.对Mysql 使用工具:mysqlslap工具 使用命令: mysqlslap -uroot pGG8697000!#--auto generate sql -auto generate sql-load typemixed-concurrency100,200 - number of queries1000-iterations10 - number-int-cols7 - number-charcols13auto genera…...
将程序添加至右键菜单
将程序添加至右键菜单 手动导入 如果要将cmder添加至右键菜单。可以通过编写reg注册表方式添加 也可以在路径HKEY_CLASSES_ROOT\Directory\Background\shell中右击添加 创建项commadn 编写reg注册表 [HKEY_CLASSES_ROOT\Directory\Background\shell\cmder]为注册表地址 Wi…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...
