当前位置: 首页 > news >正文

猿创征文|深度剖析复杂的菱形继承与菱形虚拟继承

学习导航

      • (1)问题引入
      • (2)基本概念
      • (3)底层原理
      • (4)全文总结

(1)问题引入

​  C++是允许多继承的,所谓多继承就是一个子类有两个或以上的直接父类。如果存在多继承,那么就一定会存在菱形继承,菱形继承是多继承的一种特殊情况:

image-20220903113541426

image-20220903115333783

 菱形继承似乎很实用的,但存在两个严重的问题:冗余性与二义性。以上图为例加以说明:

  1. 冗余性:
    所有的 Assistant 对象都存有两份 Person 基类,从而造成空间冗余浪费
  2. 二义性:
    由于所有的 Assistant 对象都存有两份 Person 基类,所以尝试使用Assistant 对象中 Person 的某一成员时就会存在歧义
image-20220903125536924

​  虽然二义性的问题可以通过作用域分解运算符::来显式指定,但是冗余的问题始终无法解决,于是C++大佬设计出了虚拟继承的继承方式来解决上述两个问题。

(2)基本概念

​  虚拟继承的作用是将一个指定的基类的成员实例共享给也从这个基类型直接或间接派生的其它类。
​  举例来说,如果类 StudentTeacher 各自虚继承了类 Person ,那么 Assistant 的对象就只会包含一套Person 的实例数据。
​  在C++中,基类可以在继承方式前加上virtual来声明虚继承关系,如下:

class Person
{};

class Student :virtual public Person
{};

class Teacher :virtual public Person
{};

class Assistant:public Student, public Teacher
{};

​  由于虚基类是多个由派生类共享的基类,因此由谁来完成初始化虚基类必须明确。C++标准规定,由最派生类直接初始化虚基类。

(3)底层原理

我们通过下面的代码并观察内存来加以验证:

class Person
{
protected:
	int p;
};

class Student :public Person
{
protected:
	int s;
};

class Teacher :public Person
{
protected:
	int t;
};

class Assistant : public Student, public Teacher
{
public:
	int a;
};

int main()
{
	Assistant A;
	A.a = 1;

	A.s = 2;
	A.Student::p = 3;

	A.t = 4;
	A.Teacher::p = 5;
	return 0;
}

image-20220903183054406

​  通过监视我们不难发现,Assistant 直接继承的父类(StudentTeacher)都保存有一份 Person ,这就是造成菱形继承冗余性和二义性的“罪魁祸首”。

​  现在我们使 Student 类和 Teacher 类虚拟继承 Person 基类,再次观察内存,比较和前者的不同(注:这是按32位方式编译的结果,64位则有所不同)

image-20220903185835902

我们可以观察到:

  1. Person 的成员变量被单独存放,并为所有派生类所共享,从而解决了冗余性和二义性的问题。
  2. 对于间接继承了虚基类的 StudentTeacher ,也必须能直接访问其虚继承来的祖先类,也即应知道其虚继承来的祖先类的地址偏移值。以 Student 为例说明,其中不仅存储了Student 成员变量的值,还存放了一个指针,这个指针也叫虚基表指针。指针所指向空间的下一个位置,便存放着 Student 距离基类 Person 的偏移量,从而也就解决了如何定位的问题。

(4)全文总结

  1. 有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,而虚拟继承的底层实现相当复杂,所以一般不建议设计出多继承,也一定不要设计出菱形继承,否则程序的复杂度及性能(还需要间接寻址)上都会受到影响
  2. 多继承可以认为是C++的缺陷之一,很多后来的语言都没有多继承,如Java

相关文章:

  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • 想进大厂?这份面试真题你刷了吗?
  • CentOS 7最小化安装没有ifconfig
  • 小功能⭐️Unity快捷键、路径及常用特性
  • 备份和恢复Gitlab数据
  • Kali在线安装包一些小问题
  • vue中常用的修饰符
  • 骨架图算法
  • Git --》如何在IDEA中玩转Git与GitHub?
  • C++中的继承(继承基本概念、菱形虚拟继承内存模型)
  • 怎样从零开始训练一个AI车手?
  • 【Spring Cloud】新闻头条微服务项目:文章内容安全审核(新增DFA+OCR过滤敏感词需求)
  • 猿创征文|给妈妈做个相册——在服务器上搭建Lychee相册的保姆级教程
  • 利用云服务器搭配宝塔面板解禁网易云
  • Proximal Policy Optimization Algorithms
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • Android单元测试 - 几个重要问题
  • axios 和 cookie 的那些事
  • Computed property XXX was assigned to but it has no setter
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • es6要点
  • ES6之路之模块详解
  • gf框架之分页模块(五) - 自定义分页
  • js中forEach回调同异步问题
  • Just for fun——迅速写完快速排序
  • MySQL的数据类型
  • Redash本地开发环境搭建
  • Swoft 源码剖析 - 代码自动更新机制
  • ⭐ Unity 开发bug —— 打包后shader失效或者bug (我这里用Shader做两张图片的合并发现了问题)
  • v-if和v-for连用出现的问题
  • 阿里研究院入选中国企业智库系统影响力榜
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 聚簇索引和非聚簇索引
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 项目管理碎碎念系列之一:干系人管理
  • 函数计算新功能-----支持C#函数
  • #Linux(帮助手册)
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (第一天)包装对象、作用域、创建对象
  • (二)换源+apt-get基础配置+搜狗拼音
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (一) storm的集群安装与配置
  • (转)MVC3 类型“System.Web.Mvc.ModelClientValidationRule”同时存在
  • .net Application的目录
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .NET 事件模型教程(二)
  • .net网站发布-允许更新此预编译站点
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • /bin/rm: 参数列表过长"的解决办法
  • @SuppressLint(NewApi)和@TargetApi()的区别
  • [ 数据结构 - C++] AVL树原理及实现
  • [Arduino学习] ESP8266读取DHT11数字温湿度传感器数据
  • [BZOJ4554][TJOI2016HEOI2016]游戏(匈牙利)