当前位置: 首页 > news >正文

C++ 虚继承

虚继承

首先, 虚继承和虚函数是完全无相关 的两个概念

多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题 命名冲突就是不可回避的一个。


为什么要了解虚继承这一概念?因为上一节的多继承示例程序存在着一些隐患:

多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示:
在这里插入图片描述

在多重继承中,如果发生了例如:类B继承类A,类C也继承类A,类D又同时继承了类B和类C的情况下,这个时候类A中的变量成员和函数成员继承到类D中就变成了两份,一份来自A-->B-->D,另一份来自A-->C-->D这条路径。

在一个派生类(D)中保留间接基类(A)的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的 : 因为保留多份间接基类的成员变量 不仅占用较多的存储空间,还容易产生命名冲突
例如 类A(间接基类) 有一个成员变量name,那么在 类D(最终派生类) 直接访问 name这个继承来的变量就会出现歧义(若想访问此变量 需加类名和域解析符::例如[ A::name]),因为编译器不知道它继承的这个name变量是从A -->B-->D这条路径,还是来自 A-->C-->D这条路径继承来的,这就是 多继承产生的歧义性问题 ,这在程序中是不能容忍的,解决这个问题的方法就是利用虚继承


虚继承与访问歧义

先看多继承中案例中的程序:

Teacher和Student类的个人介绍程序:

本案例中 Person基类派生Teacher和Student两个类,TeachingStudent这个类又派生自Teacher和Student两个类,完成了一个典型的菱形继承
在这里插入图片描述
TeachingStudent类的个人介绍程序:在这里插入图片描述
首先,在上个案例中 TeachingStudentTeachingStudent::introduce()(兼职教师的学生杰克的个人介绍程序) 里,对于class这个属性(上课和授课信息属性 ),因为一个兼职学生教一门课和上一门课是不可能一样的,所以应该明确的告诉编译器应该使用哪一个class属性(即应该明确指定是使用派生基类student还是派生基类teacher的class属性)
而关于兼职学生麦克的name(姓名属性),由于麦克只有一个名字,所以完全没必要告诉编译器该使用student类中的名字属性还是teacher类中的,但如今由于TeachingStudent继承了两份Person类的成员name,所以关于杰克的名字(name属性),我们 不得不明确的告诉编译器应使用哪一个name属性(也就是需要让编译器知道这个 name属性 是从Person-->Teacher这条路径还是从Person-->Student这一条路径继承来的),这便是多继承导致的歧义性

为了解决多继承导致的这个问题, 使得在最终派生类[TeachingStudent]中只保留一份间接基类[Person]的成员name ,我们在派生类TeachingStudent的直接基类(TeacherStudent的继承方式前面加上virtual关键字,这便是虚继承

虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 Person类就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,最终派生类中只会包含一份虚基类的成员


虚基类(Person类)成员的可见性

因为在虚继承的最终派生类(TeachingStudent)中只保留了一份虚基类的成员,所以该虚基类中成员(Person中的name成员)是可以直接被最终派生类访问的,不会产生二义性
如果虚基类的成员被一条派生路径所覆盖(A->B->C),那么最终派生类仍可以直接访问虚基类的成员,但是当虚基类的成员被两条以上的派生路径所覆盖(A->B->C|A->D->C),就不能直接访问了,最终派生类在访问虚基类的成员的时候必须使用 [类名+域解析符::] 指明该对象属于哪个类。

进行虚继承过后的继承关系图:
在这里插入图片描述

以上图的菱形继承为例,假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:
如果 B 和 C 中都没有 x 的定义
那么 x 将被解析为 A 的成员,此时不存在二义性,最终派生类D可以直接访问x这个变量,且这个变量属于A::x
如果 B 或 C 其中的B类定义了 x
也不会有二义性,派生类的 x 比虚基类的 x 优先级更高,最终派生类D可以直接访问x这个变量,且这个变量属于B::x
如果 B 和 C 中都定义了 x
那么直接访问 x 将产生二义性问题,最终派生类D不可以直接访问x这个变量(此时编译器将提示 "参数x不明确"),此时需要声明基类A为虚基类,使最终派生类只保留一份基类的x变量,此时便消除了二义性


注意:🎯

1.虚继承只影响从指定为虚基类的派生类(Teacher和Student中间类)中进一步派生出来的类(TeachingStudent最终派生类),它不会影响指定虚基类的派生类本身
2.在声明间接基类为虚基类(中间基类声明为virtual虚继承)之前,最终派生类的构造函数中是无法直接继承间接基类Person的构造函数的,但是在声明间接基类为虚基类后派生类的构造函数列表中必须对虚基类的构造函数进行继承


利用虚继承改良多继承中的代码

#include <iostream>
#include <string>

using namespace std;

class Person  
{
public:
	Person(string theName);
	void introduce();//介绍信息程序
protected:
	string name;
};

class Teacher : virtual public Person
{
public:
	Teacher(string theName, string theClass);
	void teach();//老师授课程序
	void introduce();//介绍信息程序
protected:
	string classes;
};

class Student : virtual public Person
{
public:
	Student(string theName, string theClass);

	void attendClass();//学生上课程序
	void introduce();//介绍信息程序 
protected:
	string classes;
};

class TeachingStudent :public Student, public Teacher
{
public:
	TeachingStudent(string theName, string classTeaching, string classAttending);
	void introduce();//介绍信息程序
};

Person::Person(string theName)
{
	name = theName;
}

void Person::introduce()
{
	cout << "大家好,我是" << name << "。\n\n";
}


//-----------------------------------------------------------------------------------------------------

Teacher::Teacher(string theName, string theClass) :Person(theName)
{
	classes = theClass;
}

void Teacher::teach()
{
	cout << name << "教" << classes << "\n\n";
}

void Teacher::introduce()
{
	cout << "大家好,我是" << name << ",我教" << classes << "\n\n";
}

//-----------------------------------------------------------------------------------------------------

Student::Student(string theName, string theClass) :Person(theName)
{
	classes = theClass;
}

void Student::attendClass()
{
	cout << name << "加入" << classes << "学习\n\n";
}


void Student::introduce()
{
	cout << "大家好,我是" << name << "我在" << classes << "学习\n\n";
}

//-----------------------------------------------------------------------------------------------------

TeachingStudent::TeachingStudent(string theName,	string classTeaching, string classAttending)
:Teacher(theName, classTeaching), Student(theName, classAttending),Person(theName)
{}

void TeachingStudent::introduce()
{
	cout << "大家好,我是" << name << "我在教" << Teacher::classes << ",";
	cout << "同时我在" << Student::classes << "学习\n\n";
}

int main()
{
	Person* per = new Teacher("张老师", "奥数班");
	per->introduce();

	Teacher teacher("张老师", "奥数班");
	Student student("小明", "奥数班");
	TeachingStudent teacherstudent("杰克", "英语班", "奥数班");

	teacher.introduce();
	teacher.teach();
	student.introduce();
	student.attendClass();
	teacherstudent.introduce();
	teacherstudent.teach();
	teacherstudent.attendClass();
	return 0;
}

运行结果:
在这里插入图片描述


总结:

虚继承是解决C++多重继承问题的手段,从不同基类继承途径继承来的同一基类,会在子类中存在多份的拷贝,这将存在两个问题:
1.浪费存储空间
2.存在二义性问题(通常情况下,可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类中有基类的拷贝)中的基类对象的地址,但是由于多重继承可能存在一个基类的多份拷贝,这就出现了二义性)

可以看到,使用多继承经常会出现二义性问题,必须十分小心。上面的例子是简单的,如果继承的层次再多一些,关系更复杂一些,程序员就很容易陷人迷魂阵,程序的编写、调试和维护工作都会变得更加困难,因此我不提倡在程序中使用多继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多继承 能用单一继承解决的问题就不要使用多继承

来自http://c.biancheng.net/view/2280.html

相关文章:

  • C++ 错误处理和调试(编写代码前的准备工作)
  • 将整型数字转换为大写汉字的自定义函数,如转换为'壹贰
  • C++ assert函数与捕获异常
  • WCF开发日志 -- OEA里面的WCF设计
  • C++ 内存分配
  • C++ 动态内存管理
  • IOS学习资源
  • C++ 从函数或方法返回动态内存(函数指针与指针函数)
  • PHP实现多web服务器共享SESSION数据-session数据写入mysql数据库
  • C++ 副本构造器
  • C++常识之——C++中堆和栈的区别,自由存储区、全局/静态存储区和常量存储区...
  • C++ 高级强制类型转换
  • thinkphp的一些类库
  • C++ 指针和引用
  • 9月24号忘记
  • 345-反转字符串中的元音字母
  • ComponentOne 2017 V2版本正式发布
  • ERLANG 网工修炼笔记 ---- UDP
  • Git初体验
  • iOS 系统授权开发
  • JavaScript实现分页效果
  • mysql 5.6 原生Online DDL解析
  • React as a UI Runtime(五、列表)
  • 从重复到重用
  • 理清楚Vue的结构
  • 浏览器缓存机制分析
  • 码农张的Bug人生 - 见面之礼
  • 面试总结JavaScript篇
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • ​香农与信息论三大定律
  • #define、const、typedef的差别
  • #laravel 通过手动安装依赖PHPExcel#
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (深度全面解析)ChatGPT的重大更新给创业者带来了哪些红利机会
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .Net 8.0 新的变化
  • .NET Core WebAPI中封装Swagger配置
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .NET企业级应用架构设计系列之结尾篇
  • .pyc文件是什么?
  • .set 数据导入matlab,设置变量导入选项 - MATLAB setvaropts - MathWorks 中国
  • /dev/sda2 is mounted; will not make a filesystem here!
  • [ vulhub漏洞复现篇 ] Apache APISIX 默认密钥漏洞 CVE-2020-13945
  • [ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例
  • [Android] Upload package to device fails #2720
  • [bzoj4240] 有趣的家庭菜园
  • [C#]手把手教你打造Socket的TCP通讯连接(一)
  • [C#7] 1.Tuples(元组)
  • [CareerCup] 14.5 Object Reflection 对象反射