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类的个人介绍程序:
首先,在上个案例中 TeachingStudent类的 TeachingStudent::introduce()
(兼职教师的学生杰克的个人介绍程序) 里,对于class这个属性(上课和授课信息属性 ),因为一个兼职学生教一门课和上一门课是不可能一样的,所以应该明确的告诉编译器应该使用哪一个class属性(即应该明确指定是使用派生基类student还是派生基类teacher的class属性)
而关于兼职学生麦克的name(姓名属性),由于麦克只有一个名字,所以完全没必要告诉编译器该使用student类中的名字属性还是teacher类中的,但如今由于TeachingStudent类继承了两份Person类的成员name,所以关于杰克的名字(name属性),我们 不得不明确的告诉编译器应使用哪一个name属性(也就是需要让编译器知道这个 name属性 是从Person-->Teacher
这条路径还是从Person-->Student
这一条路径继承来的),这便是多继承导致的歧义性。
为了解决多继承导致的这个问题, 使得在最终派生类[TeachingStudent]中只保留一份间接基类[Person]的成员name ,我们在派生类TeachingStudent
的直接基类(Teacher
和Student
的继承方式前面加上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