多重继承
多重继承就是一个派生类同时继承多个基类,同样也分为公有继承,私有继承和保护继承三种,这三种继承可以实现的关系跟单继承一样,但是多重继承会带来很多新的问题,这里以公有继承为例:
假设我们要实现下面的继承关系:

这会带来一个问题。前面说到,使用单继承的时候,派生类之中会有一个基类,派生类只是在基类的数据上额外增加了一些数据。也就是说每一个从基类派生出来的派生类对象都会维护一个基类对象,这就是问题所在。在上面的继承关系中,Worker和Adult都继承了同一个基类,而Singer同时继承了Worker和Adult。也就是说,在创建一个Singer对象的时候,这个Singer对象中分别有一个Worker和Adult对象。而这两个对象中又都有一个Person对象,这样就会造成Singer对象中有两个Person对象。有时候这可能是我们希望的结果,但是对于现在这个情况来说,显然不是。
下面是具体的实现
class Person{
private:
string name;
public:
Person();
Person(string n){
name = n;
}
void showName(){
cout << "Person name is: " << name << endl;
}
};
class Worker : public Person{
private:
int workAge;
public:
Worker();
Worker(int w, string n): Person(n){
workAge = w;
}
void showWorkAge(){
cout << "Work age is: " << workAge << endl;
}
};
class Adult : public Person{
private:
int age;
public:
Adult();
Adult(int a, string n): Person(n){
age = a;
}
void showAge(){
cout << "This adult is " << age << " years old." << endl;
}
};
class Singer : public Worker, public Adult{
private:
string type;
public:
Singer();
Singer(string t, int w, int age, string n):Worker(w, n), Adult(age, n){
type = t;
}
void showType(){
cout << "This is a " << type << " singer." << endl;
}
};
Singer s("Pop", 5, 26, "Tommy");
s.showAge();
s.showType();
s.showWorkAge();
s.showName(); //非法,编译器不知道要调用哪个基类中的showName()方法
这里要额外注意继承关系之间构造函数参数的传递方式,都是使用构造函数初始化列表的方式传递参数。其中参数n从最下层的Singer类中经过层层传递,最终到达最上层的基类Person。
这种实现会导致一个Singer对象中同时存在两个相同的Person对象,这就会出现问题。在公有继承时,可以从派生对象外部调用基类的public方法。但是在这里如果从Singer对象调用Person.showName()会产生错误,因为Singer对象中同时存在两个相同的Person对象,编译器不知道该调用哪一个。
可以通过在调用的时候显式说明调用哪个基类中的函数,或者在Singer类声明一个函数调用想用的基类函数来解决:
Singer s("Pop", 5, 26, "Tommy");
s.Worker::showName();
//或者
class Singer : public Worker, public Adult{
public:
void show(){
Worker::showName();
}
};
另一个解决的方式就是使用虚基类重载,使Worker和Adult类虚继承Person类。其关系如下:

实现虚基类继承的时候,需要注意几个要点:
- 声明继承关系的时候要加上
virtual关键字。 - 对于同一个虚基类,不管继承了几个相同的,生成对象的时候都只生成一个虚基类对象。
- 虚基类构造函数必须显式调用,不能通过中间派生类向上传递参数。
对于上述实现,需要改动的地方如下
class Person{}
class Worker : virtual public Person{} //使用virtual关键字声明该继承为虚继承
class Adult : public virtual Person{} //virtual和public的顺序没有规定
class Singer : public Worker, public Adult{
//参数n不会通过Worker或Adult的构造函数向上传递,所以要显式调用最上层虚基类的构造函数Person()
Singer(string t, int w, int age, string n):Person(n), Worker(w, n), Adult(age, n){
type = t;
}
}
s.showName(); //合法,对于重复的虚基类,编译器只生成了一个对象
这时,如果再定义一个Coder类用普通继承的方式继承Person类,再让Singer类继承Coder类,继承关系就会变成这样:

Singer同时通过虚基类继承和普通继承继承了Person类,这时编译器在创建Singer对象时会为所有通过虚基类创建一个共享的基类,并为其他普通继承的基类分别创建专用的基类。也就是说在这里编译器会在Singer对象中创建两个Person对象,一个用于给所有虚基类共享,一个是Coder中的基类。
当多个类中都继承了基类中的函数时,在继承链上最接近调用对象的函数最优先被调用,如果有多个相同的函数距离调用对象同样近,编译器就会报错。
并且函数的优先级只跟函数声明跟调用对象的距离有关,跟函数的可见性无关。例如,如果距离调用对象最近的一个函数是private(不可访问),编译器会报不可访问的错,而不是再去找第二近的可访问的函数。
对于以下继承关系:

当调用D.fun()时,最优先的有两个,C和E,所以编译器会报错。
同时,对于这种情况:

编译器也会报错,因为距离D最近的函数不可访问。

1357

被折叠的 条评论
为什么被折叠?



