详解C++中的菱形继承问题

 更新时间:2023年08月08日 10:54:07   作者:悲伤的鱼香肉丝er  
这篇文章主要介绍了详解C++中的菱形继承问题,在多继承结构中,存在着很多问题,比如从不同基类中继承了同名成员,派生类中也定义了同名成员,这种二义性问题很好解决,加上要访问的基类的类名限制就可以了,需要的朋友可以参考下

一、多重继承或多继承

由多个基类共同派生出新的类,这样的继承结构被称为多重继承或多继承。举个例子:

#include<iostream>
using namespace std;
class fuelEngine //燃油引擎
{
private:
	int cylindenum;//汽缸数
public:
	fuelEngine(int c = 4):cylindenum(c){}
	~fuelEngine(){}
	void start(){}
};
class electricEngine//电动引擎
{
private:
	float power;
public:
	electricEngine(float p=60):power(p){}
	~electricEngine(){}
	void Start(){}
};
class Hybirdcar:public fuelEngine,public electricEngine//混动汽车
{
public:
	Hybirdcar(int c,int p):fuelEngine(c),electricEngine(p){}
	~Hybirdcar(){}
};

混动汽车类是由电动引擎类和燃油引擎类共同派生出来的派生类,这种继承结构就为多继承。

二、菱形继承(二义性和数据冗余问题)

在多继承结构中,存在着很多问题,比如从不同基类中继承了同名成员,派生类中也定义了同名成员,这种二义性问题很好解决,加上要访问的基类的类名限制就可以了,例:

#include<iostream>
using namespace std;
class Base1
{
  public:
    int var;
    void fun()
    {
        cout<<"Member of Base1"<<endl;
    }
};
class Base2
{
  public:
    int var;
    void fun()
    {
        cout<<"Member of Base2"<<endl;
    }
};
class Derived:public Base1,public Base2
{
  public:
    int var;
    void fun(){cout<<"Member of Derived"<<endl;}
};
int main()
{
    Derived d;
    Derived *p = &d;
    //访问Derived类成员
    d.var =1;
    d.fun();
    //访问Base1基类成员
    d.Base1::var=2;
    d.Base1::fun();
    //访问Base2基类成员
    p->Base2::var=3;
    p->Base2::fun();
    return 0;
}

而在多继承中还存在一种特殊情况——菱形继承。我们还是用一段代码来举例说明菱形继承:

#include<iostream>
using namespace std;
class Person
{
private:
	int _idPerson;
public:
	Person(int id) :_idPerson(id) { cout << "Create Person" << endl; }
	~Person(){}
};
class Student:public Person//学生
{
private:
	int _snum;
public:
	Student(int id,int s):Person(id),_snum(s){}
	~Student(){}
};
class Employee:public Person//职工
{
private:
	int _enum;
public:
	Employee(int id,int e):Person(id),_enum(e){}
	~Employee(){}
};
class GStudent:public Student//研究生
{
private:
	int _gsnum;
public:
	GStudent(int g,int s,int id):Student(s,id),_gsnum(g){}
	~GStudent(){}
};
class EGStudent :public GStudent,public Employee//在职研究生
{
private:
	int _egsnum;
public:
	EGStudent(int es,int s,int g,int e,int sid,int eid)
		:GStudent(s,g,sid),Employee(e,eid),_egsnum(es){}
	~EGStudent(){}
};
int main()
{
	EGStudent egs(1,2,3,4,5,6);
	return 0;
}

代码中设计了Person类,由Person类派生出学生Student类和职工Employee类,Student类又向下派生出研究生GStudent类,再由GStudent类和Employee类共同派生出在职研究生EGStudent类,根据此段代码的继承关系可画出示意图 :

不难发现,在菱形继承中也存在二义性,并且出现数据冗余浪费了内存空间,由基类Person的_idPerson身份证号有两条路径继承到EGStudent类中,两个身份证号在逻辑上是相同的,但在物理上被分配了不同的内存空间,是两个变量。

示例中对象egs的内存分布图如下:

那如何解决这种数据冗余和二义性问题呢?——虚继承

在C++中可以把共同基类设置为虚基类,这样从不同路径继承来的同名数据成员在内存中只有一份,虚基类定义方式:class 派生类名:virtual 访问限定符 基类类名{...};class 派生类名:访问限定符 virtual 基类类名{...};(virtual关键字只对紧随其后的基类名起作用)

在上述示例代码中,两个身份证号显然是不合理的,所以我们可以把class Person 设置成虚基类,即class Student : virtual public Person {...}; 和 class Employee : virtual public Person {...}; 

菱形继承就变成了菱形虚拟继承。修改后代码如下:

#include<iostream>
using namespace std;
class Person
{
private:
	int _idPerson;
public:
	Person(int id) :_idPerson(id) { cout << "Create Person" << endl; }
	~Person(){}
};
class Student:public virtual Person//学生
{
private:
	int _snum;
public:
	Student(int id,int s):Person(id),_snum(s){}
	~Student(){}
};
class Employee:public virtual Person//职工
{
private:
	int _enum;
public:
	Employee(int id,int e):Person(id),_enum(e){}
	~Employee(){}
};
class GStudent:public Student//研究生
{
private:
	int _gsnum;
public:
	GStudent(int g,int s,int id):Student(s,id),Person(id),_gsnum(g){}
	~GStudent(){}
};
class EGStudent :public GStudent,public Employee
{
private:
	int _egsnum;
public:
	EGStudent(int es,int s,int g,int e,int id)
		:GStudent(s,g,id),Employee(e,id),_egsnum(es),Person(id){}
	~EGStudent(){}
};
int main()
{
	EGStudent egs(1, 2, 3, 4, 5);
	Person* p = &egs;
	return 0;
}

三、菱形虚拟继承

1.虚继承中派生类对象构造过程

在派生类对象的创建过程中,

首先是虚基类的构造函数按声明的顺序进行构造,

第二批是非虚基构造函数按声明顺序构造,

第三批是成员对象的构造函数,最后是派生类自己的构造函数。

2.菱形虚拟继承对象的内存分布

 其实是通过两个指针(虚基表指针)指向了一张虚基表,虚基表中存的是偏移量,通过偏移量可以找到_idPerson的位置

有了多继承,就有菱形继承,有了菱形继承就有了菱形虚拟继承,底层实现很复杂,一般不建议设计出多继承,否则在复杂度和性能上可能都会出现问题。

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

相关文章

  • gcc中extra qualification错误的解决

    gcc中extra qualification错误的解决

    今天小编就为大家分享一篇gcc中extra qualification错误的解决,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • C++设计模式编程中Facade外观模式的使用实例解析

    C++设计模式编程中Facade外观模式的使用实例解析

    这篇文章主要介绍了C++设计模式编程中Facade外观模式的使用实例解析,外观模式的主要用途就是为子系统的复杂处理过程提供方便的调用方法,需要的朋友可以参考下
    2016-03-03
  • C语言读写配置文件的方法

    C语言读写配置文件的方法

    这篇文章主要介绍了C语言读写配置文件的方法,包括C语言读写ini配置文件所涉及的文件读写技巧,以及完整的源文件及头文件实现方法,需要的朋友可以参考下
    2015-07-07
  • 浅谈c++中的输入输出方法

    浅谈c++中的输入输出方法

    下面小编就为大家带来一篇浅谈c++中的输入输出方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • 深入分析C++中声明与定义的区别

    深入分析C++中声明与定义的区别

    C++学了这么多年你知道为什么定义类时,类的定义放在.h文件中,而类的实现放在cpp文件中。它们为什么能够关联到一起呢?你知道什么东西可以放在.h文件中,什么不能。什么东西又可以放在cpp文件中。如果你忘记了或是压根就不明白,那么读过此文你会清晰无比!!
    2014-09-09
  • C语言实现进程5状态模型的状态机

    C语言实现进程5状态模型的状态机

    状态机在实际工作开发中应用非常广泛,用这幅图就可以很清晰的表达整个状态的流转。本篇通过C语言实现一个简单的进程5状态模型的状态机,让大家熟悉一下状态机的魅力,需要的可以参考一下
    2022-10-10
  • C++实践数组类运算的实现参考

    C++实践数组类运算的实现参考

    今天小编就为大家分享一篇关于C++实践数组类运算的实现参考,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-02-02
  • 基于SVN源码服务器搭建(详细教程分析)

    基于SVN源码服务器搭建(详细教程分析)

    本篇文章是对SVN源码服务器搭建进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • C++实现教职工信息管理系统课程设计

    C++实现教职工信息管理系统课程设计

    这篇文章主要为大家详细介绍了C++实现教职工信息管理系统课程设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C++实现视频流转换为图片方式

    C++实现视频流转换为图片方式

    今天小编就为大家分享一篇C++实现视频流转换为图片方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12

最新评论