C++菱形继承及解决方法详解

 更新时间:2023年08月08日 10:45:09   作者:魏天乐大帅哥  
这篇文章主要介绍了C++菱形继承及解决方法详解,在多继承结构中,存在着很多问题,比如从不同基类中继承了同名成员,派生类中也定义了同名成员,这种二义性问题很好解决,加上要访问的基类的类名限制就可以了,需要的朋友可以参考下

菱形继承

继承是类和类之间的关系,是代码复用的重要手段,允许在保持原有类结构的基础上进行扩展,创建的新类与原有的类类似,只是多了几个成员变量和成员函数。

需要注意的是:

1.若不表明是以何种方式继承,使用关键字class时默认是私有继承,使用struct关键字时默认公有继承

2.上面的不可访问是指派生类对象不管是在类里还是类外都不能不访问

在谈菱形继承之前,需要先了解单继承和多继承:

  • 单继承是一个子类只有一个直接父类时称这个继承关系是单继承
  • 一个子类有两个或两个以上的直接父类时称这个继承关系为多继承
  • 继承中子类中成员的排列次序与继承次序有关
  • (也就是说先继承的类,类中成员的地址越靠下)

菱形继承:

  • 两个子类同时继承一个父类,而又有子类同时继承这两个子类

缺点:

  • 导致数据冗余和二义性的问题

解决方法:

  • 对造成二义性的属性使用域访问限定符,这样从本质上并没有解决二义性
  • 想要解决二义性和数据冗余,这就需要用到虚拟继承。属性只会生成一份,要调用它需要使用虚基表指针,虚基表指针指向虚基表,虚基表中存在偏移量,通过偏移量就可以找到共有的那个属性。

封装,继承,多态。这是C++语言的三大特性,而每次在谈到继承时我们不可避免的要谈到一个很重要的问题——菱形继承。

多继承,呈菱形状

在这里插入图片描述

菱形继承代码:

class A 
{
public:
    A() {}
    int _a ;
};
class B :public A 
{
public:
    //会存在父的int a;
    B() {}
    int _b ;
};
class C :public A 
{
public:
   //会存在父的int a;
    C() {}
    int _c ;
};
class D :public B,public C 
{
public:
   //会存在父的int a;    问题来了这个a是  父亲b中的a 还是 父亲c中的a -- 二义性;
   //父的int b;
   //父的int c;
    D() {}
    int _d ;
};
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    b._d = 5;
    cout << d._a << endl;//报错  提示b.a具有二义性
    return 0;
}

出现二义性变量的内存布局

在这里插入图片描述

可以看到上图中紫色框两部分,是出现二义性的两份A::_a变量,编译器无法自主确定需要用哪一个,可以d::A_a 或者 d::B_a这样使用;

应对方案

虚继承 vitrual

vitrual修饰使派生类出现二义性的父类继承部分(菱形的腰部)

class A 
{
public:
    A() {}
    int _a ;
};
class B :virtual public A //virtual修饰
{
public:
    //父的int a;
    B() {}
    int _b;
};
class C :virtual public A  //virtual修饰
{
public:
   //父的int a;
    C() {}
    int _c ;
};
class D :public B,public C 
{
public:
    //自己的 int a;(二义性的父亲继承经virtual修饰)
   //父的int b;
   //父的int c;
    D() {}
    int _d;
};
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    b._d = 5;
    cout << d._a << endl;//没问题,输出2,这个2是D类成员通过继承,使得d自己独有且只有一份的 A::_a成员变量;
    return 0;
}

解决二义性变量内存布局–虚基表

在这里插入图片描述

可以看到原先存放两个二义性数据A::a和B::a的位置,变成了一个地址?

这个两个地址就叫虚基表指针

通过对虚基表的进一步内存研究,发现了虚基表紧着得下一个位置存放了一个偏移量,这个偏移量是存放该虚基表指针的内存位置,与当前派生类独有一份的成员变量_a之间的偏移长度;

这样我们直接使用d::a的时候,因为独有一份,也不会出现数据二义性的问题了;

注意,这和多态中的虚表(虚函数表两回事)

其次,虚基表的指针,经过测试,也是某个多继承派生类的多对象共用的;

eg: D d1,d2; 其中d1,d2两个对象 上图的这两张虚基表指针是一样的,原因很简单,D类类型都一样,那么某个位置起,到另一个相对位置偏移量肯定是固定的!

(同类型的虚表指针也如此,复用节省空间嘛)

感悟

继承,多态无疑为我们创造了很多的价值,但是像菱形继承这种弊端也是存在的,本质是多继承而引起的问题,在一些语言禁止了多继承的行为,总之有利有弊,虽然C++允许了多继承,但还是尽量别写多继承这种模式,复杂度和出现问题的概率都很大;

关于代码复用等的另一种关系-组合

继承是每个派生类都能相对于继承is-a的关系; 每个派生类对象都是一个基类对象; 耦合度高

在这里插入图片描述

优先考虑组合has-a的关系; eg: 汽车-轮胎,has-a,组合(class 轮胎 作为class 车的成员嵌套) 耦合度低

在这里插入图片描述

到此这篇关于C++菱形继承及解决方法详解的文章就介绍到这了,更多相关C++菱形继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言实现简单计算器程序

    C语言实现简单计算器程序

    这篇文章主要为大家详细介绍了C语言实现简单计算器程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-02-02
  • VisualStudio2019解决scanf函数报错问题

    VisualStudio2019解决scanf函数报错问题

    在 Visual Studio 2019 编辑代码时,前期刚刚接触到VS编译器时存在的困惑,当用scanf()函数,进行输入时,在运行的时候编译器会出现警告报错,本文就来介绍一下解决方法
    2023-08-08
  • C++实现 单例模式实例详解

    C++实现 单例模式实例详解

    这篇文章主要介绍了C++实现 单例模式实例详解的相关资料,需要的朋友可以参考下
    2017-05-05
  • C++/Php/Python 语言执行shell命令的方法(推荐)

    C++/Php/Python 语言执行shell命令的方法(推荐)

    下面小编就为大家带来一篇C++/Php/Python 语言执行shell命令的方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • 基于Linux系统调用--getrlimit()与setrlimit()函数的方法

    基于Linux系统调用--getrlimit()与setrlimit()函数的方法

    本篇文章是对在Linux系统中调用getrlimit()与setrlimit()函数的方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C++实现扫雷游戏(控制台不闪屏版)

    C++实现扫雷游戏(控制台不闪屏版)

    这篇文章主要为大家详细介绍了C++实现扫雷游戏,控制台不闪屏版,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • QT实战之实现图片浏览系统

    QT实战之实现图片浏览系统

    这篇文章主要介绍了如何利用QT编写一个图片浏览系统,可以支持自动播放,左右拖动切换,点击列表切换,点击按钮切换等功能,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-04-04
  • C语言实现扫雷游戏详解(附源码)

    C语言实现扫雷游戏详解(附源码)

    大家好,本篇文章主要讲的是C语言实现扫雷游戏详解(附源码),感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • C语言编程中分配内存空间的相关函数

    C语言编程中分配内存空间的相关函数

    这篇文章主要介绍了C语言编程中分配内存空间的相关函数,分别是malloc()函数和calloc()函数,需要的朋友可以参考下
    2015-08-08
  • C++模版函数详解

    C++模版函数详解

    C++中的模版总体可以分为两大类:模版函数、模版类。本篇文章先写模版函数,需要的朋友可以参考下
    2017-02-02

最新评论