【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

相关文章

linux生产者,消费者问题

pthread_cond_wait() :用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast来唤醒它。 pthread_cond_wait() 必须与pthread_mutex 配套使用。pthread_cond_wait()函数一进入wait状态就会自动release mutex。当其他线程通过pthread

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

C++的模板(八):子系统

平常所见的大部分模板代码,模板所传的参数类型,到了模板里面,或实例化为对象,或嵌入模板内部结构中,或在模板内又派生了子类。不管怎样,最终他们在模板内,直接或间接,都实例化成对象了。 但这不是唯一的用法。试想一下。如果在模板内限制调用参数类型的构造函数会发生什么?参数类的对象在模板内无法构造。他们只能从模板的成员函数传入。模板不保存这些对象或者只保存他们的指针。因为构造函数被分离,这些指针在模板外

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

C++工程编译链接错误汇总VisualStudio

目录 一些小的知识点 make工具 可以使用windows下的事件查看器崩溃的地方 dumpbin工具查看dll是32位还是64位的 _MSC_VER .cc 和.cpp 【VC++目录中的包含目录】 vs 【C/C++常规中的附加包含目录】——头文件所在目录如何怎么添加,添加了以后搜索头文件就会到这些个路径下搜索了 include<> 和 include"" WinMain 和

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode