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

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

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

前言:

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

目录

一、单继承和多继承

C++单继承

C++多继承

多继承的复杂性

二、菱形继承

问题1:冗余性

问题2:二义性

三、虚拟继承

四、总结


一、单继承和多继承

C++单继承

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

单继承的语法如下:

cpp 复制代码
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++多继承

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

多继承的语法如下:

cpp 复制代码
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都有一个同名的函数,而在派生类中没有明确指定调用哪一个,就会产生二义性错误。

下面来看一个例子:

cpp 复制代码
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继承同一个虚基类时,虚基类的成员变量和成员函数在子类中只会存在一份,避免了冗余性和二义性问题。

虚继承的基本语法如下:

cpp 复制代码
class BaseClass {
public:
    int var;
};

class LeftChild : public virtual BaseClass {
public:
    // ...
};

class RightChild : public virtual BaseClass {
public:
    // ...
};

class FinalChild : public LeftChild, public RightChild {
public:
    // ...
};

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

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

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

四、总结

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

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

相关推荐
湫ccc22 分钟前
《Python基础》之字符串格式化输出
开发语言·python
Red Red25 分钟前
网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务
网络·笔记·学习·安全·web安全
mqiqe1 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin1 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust