【C++进阶学习】第三弹——菱形继承和虚拟继承——菱形继承的二义性和数据冗余问题

本文主要是介绍【C++进阶学习】第三弹——菱形继承和虚拟继承——菱形继承的二义性和数据冗余问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

继承(上):【C++进阶学习】第一弹——继承(上)——探索代码复用的乐趣-CSDN博客

继承(下):【C++进阶学习】第二弹——继承(下)——挖掘继承深处的奥秘-CSDN博客

前言:

在前面,我们已经讲过继承的相关知识,今天我们来将一个由继承拓展出来的很重要的知识,那就是——菱形继承和虚拟继承及相关知识讲解

目录

一、单继承和多继承

C++单继承

C++多继承

多继承的复杂性

二、菱形继承

问题1:冗余性

问题2:二义性

三、虚拟继承

四、总结


一、单继承和多继承

C++单继承

在C++中,单继承是指一个类只能继承自一个基类。这意味着派生类只能有一个直接的基类。

单继承的语法如下:

class Base {
public:void baseFunction() {cout << "Base function" << endl;}
};class Derived : public Base {
public:void derivedFunction() {cout << "Derived function" << endl;}
};

在这个例子中,Derived 类继承自 Base 类。Derived 类可以访问 Base 类中声明为 public 的成员。

C++多继承

多继承允许一个类继承自多个基类。这意味着派生类可以有多个直接的基类。

多继承的语法如下:

class Base1 {
public:void base1Function() {cout << "Base1 function" << endl;}
};class Base2 {
public:void base2Function() {cout << "Base2 function" << endl;}
};class Derived : public Base1, public Base2 {
public:void derivedFunction() {cout << "Derived function" << endl;}
};

在这个例子中,Derived 类同时继承自 Base1Base2Derived 类可以访问两个基类中声明为 public 的成员。

多继承的复杂性

多继承虽然功能强大,但也带来了一些复杂性,例如菱形继承问题。菱形继承很容易带来冗余性和二义性,这些就需要我们用虚拟继承来解决,这些问题挺重要,我们往下看

二、菱形继承

C++中的菱形继承是指在类的继承关系中,存在两个或更多个直接或间接的基类,它们之间形成了一个类似菱形的结构。这种继承结构通常出现在多层继承中,当一个派生类同时从两个不同的基类继承到了同一个基类时,就可能导致问题。

问题1:冗余性

冗余性主要体现在代码的重复。在菱形继承中,派生类会继承两个基类的所有公共和私有成员。如果这些成员在两个基类中定义了相同的实现,那么在派生类中可能会有重复的代码,这不仅增加了代码量,还可能导致维护困难,因为需要在所有相关的实现中同步更新。

问题2:二义性

二义性是指在菱形继承的情况下,派生类可能会有两个或更多的基类提供了相同的函数或数据成员,这在调用时会导致编译器无法确定调用哪个版本。例如,如果基类A和B都有一个同名的函数,而在派生类中没有明确指定调用哪一个,就会产生二义性错误。

下面来看一个例子:

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";
}

总之,菱形继承在C++中是一个复杂且容易引发问题的特性,需要谨慎使用并结合其他设计原则来确保代码的清晰和可维护性。

下面我们来讲解一种解决上面问题的方法——虚拟继承

三、虚拟继承

虚继承是一种特殊的继承方式,用于解决菱形继承中的冗余性和二义性问题。了解虚继承的相关知识点有助于更好地使用它。

虚基类:在虚继承中,被继承的类被称为虚基类。

虚基类的成员变量和成员函数在子类中只会存在一份,避免了冗余性问题。


1、虚继承的语法:虚继承的语法与普通继承类似,只需在继承语句前加上关键字 virtual,如 class SubClass : public virtual BaseClass { ... };。
2、虚表:虚继承会在运行时为每个对象创建一个虚表,用于记录虚基类的实际地址,以便在运行时正确地访问虚基类的成员变量和成员函数。(这个知识点还是比较重要的,因为一些原因,我这里并不会讲,感兴趣的可以自己去网上搜一下视频,或者与我私聊)
3、构造函数和析构函数:当虚继承时,构造函数和析构函数会按照继承顺序依次调用,从而确保虚基类的构造和析构正确地执行。
4、访问控制:由于虚继承的存在,可能会导致访问控制问题,例如在子类中无法直接访问虚基类的成员变量或成员函数。这时可以通过使用using语句或显式限定符来解决。
5、空类的大小:虚继承会导致空类的大小不为 0,因为需要为每个对象创建一个虚表(vtable)。
6、多继承时的虚继承:当多个类同时virtually继承同一个虚基类时,虚基类的成员变量和成员函数在子类中只会存在一份,避免了冗余性和二义性问题。
 

虚继承的基本语法如下:

class BaseClass {
public:int var;
};class LeftChild : public virtual BaseClass {
public:// ...
};class RightChild : public virtual BaseClass {
public:// ...
};class FinalChild : public LeftChild, public RightChild {
public:// ...
};

在上面的示例中,LeftChild RightChild virtually继承自 BaseClass,这样在 FinalChild 继承 LeftChildRightChild 时,就不会再继承 BaseClass 的两份副本,避免了冗余性问题。此时,BaseClass 的成员变量 varFinalChild 中只有一份,并且不会发生二义性问题。

需要注意的是,虚继承会带来一些额外的开销,因为需要在运行时维护一个表来记录虚继承的类的实际地址(这就是上面第2点提到的虚表),这会导致一些性能上的损失(至于是何种损失及如何损失感兴趣的可以私下搜一下)。因此,虚继承应该谨慎使用,只在必要时才使用。

总之,C++ 通过虚继承解决了菱形继承中的冗余性和二义性问题,使得在使用继承时更加灵活和安全。

四、总结

以上就是C++多继承中菱形继承及如何解决它所带来的问题的相关知识点,上面有些知识点仅仅是点到,并没有详细讲解,比如虚表等知识点,这些知识其实也相当重要,但是由于文字较难叙述的问题,我并没有展开讲解,感兴趣的可以私下找下视频学习一下,或者私我。

感谢各位大佬观看,创作不易,还请各位大佬点赞支持一下!!!

这篇关于【C++进阶学习】第三弹——菱形继承和虚拟继承——菱形继承的二义性和数据冗余问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1082323

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

IDEA和GIT关于文件中LF和CRLF问题及解决

《IDEA和GIT关于文件中LF和CRLF问题及解决》文章总结:因IDEA默认使用CRLF换行符导致Shell脚本在Linux运行报错,需在编辑器和Git中统一为LF,通过调整Git的core.aut... 目录问题描述问题思考解决过程总结问题描述项目软件安装shell脚本上git仓库管理,但拉取后,上l

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

idea npm install很慢问题及解决(nodejs)

《ideanpminstall很慢问题及解决(nodejs)》npm安装速度慢可通过配置国内镜像源(如淘宝)、清理缓存及切换工具解决,建议设置全局镜像(npmconfigsetregistryht... 目录idea npm install很慢(nodejs)配置国内镜像源清理缓存总结idea npm in

pycharm跑python项目易出错的问题总结

《pycharm跑python项目易出错的问题总结》:本文主要介绍pycharm跑python项目易出错问题的相关资料,当你在PyCharm中运行Python程序时遇到报错,可以按照以下步骤进行排... 1. 一定不要在pycharm终端里面创建环境安装别人的项目子模块等,有可能出现的问题就是你不报错都安装

idea突然报错Malformed \uxxxx encoding问题及解决

《idea突然报错Malformeduxxxxencoding问题及解决》Maven项目在切换Git分支时报错,提示project元素为描述符根元素,解决方法:删除Maven仓库中的resolv... 目www.chinasem.cn录问题解决方式总结问题idea 上的 maven China编程项目突然报错,是

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型: