多重继承
在派生类的派生列表中可以包含多个基类,以逗号分隔:
class Panda : public Bear,public Endangered
和单继承一样,多重继承的派生列表也只能包含已经被定义过的类,而且这些类不能是final(类被final修饰,不能被继承)的。
派生类构造函数初始化所有基类
构造一个派生类对象的同时构造并初始化它的所有基类子对象,还是以逗号来分隔:
//显示的初始化所有基类
Panda::Panda(string s1,string s2)
:Bear(s1),Endangered(s2){}
派生类的构造函数初始化列表将实参分别传递给每个直接基类,其中基类的构造顺序与派生类列表中基类的出现顺序一致,而与派生类构造函数初始值列表中的基类的顺序无关。
继承的构造函数与多重继承
在C++11新标准中,允许派生类从它的一个或几个基类中继承构造函数,但是如果从多个基类中继承了相同的构造函数,程序将产生错误:
struct Base1{
Base1() = default;
Base1(const string&);
Base1(shared_ptr<int>);
};
struct Base2{
Base2() = default;
Base2(const string&);
Base2(int);
};
//错误,D1试图从两个基类中都继承D1::D1(const string&)
struct D1 : public Base1,public Base2{
using Base1::Base1;
using Base2::Base2;
};
//如果一个类从它的多个基类中都继承了相同的构造函数,那它必须定义自己的版本
struct D2 : public Base1,public Base2{
using Base1::Base1;
using Base2::Base2;
D2(const string& s):Base1(s),Base2(s){}
};
类型转换与多个基类
在只有一个基类的情况下,派生类的指针和引用可以自动转换成一个可以访问基类的指针或引用。多个基类的情况与之类似,可以令某个访问基类的指针或引用直接指向一个派生类对象。
但是编译器不会在派生类向基类的几种转换中进行比较和选择,在它看来转换到任一基类都是一样好的,因此如果两个基类中存在同名的重载形式时,可能会有二义性错误,例如:
//Bear和Endangered类都是Panda的基类
void print(Bear&);
void print(Endangered&);
Panda p("tt");
print(p); //二义性错误
与一个基类的继承一样,对象、指针或引用的静态类型决定了能够使用的成员,例如通过基类1的指针访问派生类的对象时,只能使用属于基类1的接口与属性,派生类的特有部分和其他基类的部分都是不可见的。
多重继承下的类作用域
在只有一个基类的情况下,派生类的作用域嵌套在直接基类和间接基类的作用域中,查找过程沿着继承体系自底向上进行,知道找到所需名字,派生类的名字将隐藏基类的同名成员。
在多重继承下,相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到,则不加前缀限定符直接使用该名字将引发二义性。
虚继承
虚继承的目的是令某个类做出声明,承诺愿意共享它的基类。其中共享的基类子对象被称为虚基类,不论虚基类在继承体系下被继承多少次,在派生类中都只包含唯一一个共享的虚基类子对象(菱形继承)。
使用虚基类
指定虚基类的方式是在派生列表中添加关键字virtual:
//public和virtual的顺序随意
class Raccon : public virtual ZooAnimal{};
class Bear : virtual public ZooAnimal{};
class Panda : public Raccon,public Bear{};
因为Raccon和Bear继承ZooAnimal的方式都是虚继承,因此在Panda中只有一个ZooAnimal基类部分。
虚基类成员的可见性
因为在每个共享的虚基类中只有唯一一个共享的子对象,所以该基类的成员可以被直接访问,并且不会产生二义性。
例如:类B有一个成员名为x,类D1和D2是通过虚继承的方式继承自B,类D通过多重继承继承D1和D2,如果通过D来访问x,则可能的情况有:
1.在D1和D2中都没有对x的定义,则x将被解析为B的成员,此时不存在二义性;
2.在D1或D2中有对x的定义,则x将被解析成D1或D2的成员,其优先级高于虚基类,同样没有二义性;
3.在D1和D2中都有对x的定义,直接访问x将产生二义性。
构造函数与虚继承
在虚派生中,虚基类是由最低层的派生类初始化的。
为理解这一规则,不妨假设以普通规则来处理初始化任务时会出现什么情况:虚基类将在多条路径下被重复初始化。
当然继承体系中的每个类都可能在某个时刻成为“最低层的派生类”,只要能创建虚基类的派生类对象,该派生类的构造函数就必须初始化它的虚基类。例如:B是基类,D1和D2虚继承自B,D继承自D1和D2,当创建D1的对象时,有D1的构造函数直接初始化其基类B的部分;当创建D的对象时,D来负责初始化B基类部分,即使B不是D的直接基类。
虚继承的对象的构造方式
含有虚基类的对象的构造顺序与一般的顺序稍有区别:首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照基类在派生列表中出现的顺序依次对其进行初始化。
如果派生类没有显示的初始化虚基类,则虚基类的默认构造函数将被调用,如果其没有默认构造函数,则代码将发生错误。
如果有多个虚基类,那么这些虚的子对象按照他们在派生列表中出现的顺序来进行构造,例如:
class B{};
class D1:virtual public B{};
class D2{};
class D3{};
class D : public D2,public D1,virtual public D3{}
以上进行构造的顺序应该是先构造B,再构造D3,再是D2,最后是D1。
析构函数的顺序相反。