一、多重继承与虚继承
1、多重继承
在派生类的派生列表中可以包含多个基类,每个基类包含一个可选的访问说明符。
多重继承的派生列表也只能包含已经定义过的类,而且这些类不能是final的。对于派生类能够继承的基类个数,C++没有进行特殊规定;但是在某个给定的派生列表中,同一个基类只能出现一次。
1)多重继承的派生类从每个基类中继承状态
在多重继承关系中,派生类的对象包含有每个基类的子对象。
2)派生类构造函数初始化所有基类
构造一个派生类的对象将同时构造并初始化它的所有基类子对象。与从一个基类进行的派生一样,多重继承的派生类的构造函数初始值也只能初始化它的直接基类。
派生类的构造函数将初始值列表将实参分别传递给每个直接基类。其中基类的构造顺序与派生列表中基类的出现顺序保持一致,而与派生类构造函数初始值列表中基类的顺序无关。
3)继承的构造函数与多重继承
在C++11新标准中,允许派生类从它的一个或几个基类中继承构造函数。但是如果从多个基类中继承了相同的构造函数(即形参列表完全相同),则程序将产生错误。
如果一个类从它的多个基类中继承了相同的构造函数,则这个类必须为该构造函数定义它自己的版本。
4)析构函数与多重继承
派生类的析构函数只负责清除派生类本身分配的资源,派生类的成员及基类都是自动销毁的。
析构函数的调用顺序正好与构造函数相反。
5)多重继承的派生类的拷贝与移动操作
与只有一个基类的继承一样,多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符,则必须在完整的对象上执行拷贝、移动或赋值操作。只有当派生类使用的是合成版本的拷贝、移动或赋值成员时,才会自动对其基类分别执行这些操作。在合成的拷贝控制成员中,每个基类分别使用自己的对应成员隐式地完成构造、赋值或销毁等工作。
2、类型转换与多个基类
在只有一个基类的情况下,派生类的指针或引用能自动转换成一个可访问基类的指针或引用。多个基类的情况与之类似。
编译器不会再派生类向基类的几种转换中进行选择和比较,因为在它看来转换到任意一种基类都一样好。
1)基于指针类型或引用类型的查找
与只有一个基类的继承一样,对象、指针和引用的静态类型决定了我们能够使用哪些成员。
3、多重继承下的类作用域
在只有一个基类的情况下,派生类的作用域嵌套在直接基类和间接基类的作用域中。查找过程沿着继承体系自底向上进行,直到找到所需的名字。派生类的名字将隐藏基类的同名成员。
在多重继承的情况下,相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到,则对该名字的使用将具有二义性。
对于一个派生类来说,从它的几个基类中分别继承名字相同的成员是完全合法的,只不过在使用这个名字时必须明确指出它的版本。
4、虚继承
在默认情况下,派生类中含有继承链上每个类对应的子部分。如果某个类在派生过程中出现了多次,则派生类中将包含该类的多个子对象。
在C++语言中我们通过虚继承的机制解决上述问题。虚继承的目的是令某个类做出声明,承诺愿意共享它的基类。其中,共享的基类子对象称为虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含唯一一个个共享的虚基类子对象。
1)使用虚基类
我们指定虚基类的方式是在派生列表中添加关键字virtual。virtual说明符表明了一种愿望,即在后续的派生类当中共享虚基类的同一份实例 。如果某个类指定了虚基类,则该类的派生仍按常规方式进行。
1 #include <iostream> 2 #include <string> 3 4 class Base { 5 public: 6 Base() { 7 std::cout << __FUNCTION__ << std::endl; 8 } 9 }; 10 class D1 :public virtual Base { 11 public: 12 D1() :Base() { 13 std::cout << __FUNCTION__ << std::endl; 14 } 15 }; 16 class D2 :public virtual Base { 17 public: 18 D2() :Base() { 19 std::cout << __FUNCTION__ << std::endl; 20 } 21 }; 22 class D :public D1, public D2 { 23 public: 24 D() :D1(), D2() { 25 std::cout << __FUNCTION__ << std::endl; 26 } 27 }; 28 int main() 29 { 30 D d; 31 return 0; 32 }
2)支持向基类的常规类型转换
不论基类是不是虚基类,派生类对象都能被可访问基类的指针或引用操作。
3)虚基类成员的可见性
5、构造函数与虚继承
1)虚继承的对象的构造方式
含有虚基类的对象的构造顺序与一般的顺序稍有区别:首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生列表中出现的次序依次对其进行初始化。如果没有显式的初始化虚基类,则虚基类的默认构造函数被调用。
1 #include <iostream> 2 #include <string> 3 4 class Base { 5 public: 6 Base() { 7 std::cout << __FUNCTION__ << std::endl; 8 } 9 Base(int x):bx(x) { 10 std::cout << "hello Base" << std::endl; 11 } 12 private: 13 int bx; 14 }; 15 class D1 :public virtual Base { 16 public: 17 D1(int x) :Base(x), dx1(x) { 18 std::cout << __FUNCTION__ << std::endl; 19 } 20 private: 21 int dx1; 22 }; 23 class D2 :public virtual Base { 24 public: 25 D2(int x) :Base(x), dx2(x) { 26 std::cout << __FUNCTION__ << std::endl; 27 } 28 private: 29 int dx2; 30 }; 31 class D :public D1, public D2 { 32 public: 33 D(int x=0) :Base(x), D1(x), D2(x) { 34 std::cout << __FUNCTION__ << std::endl; 35 } 36 private: 37 int d; 38 }; 39 int main() 40 { 41 D d; 42 return 0; 43 }
虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关。
2)构造函数与析构函数的次序
一个类可以有多个虚基类。此时,这些虚的子对象按照它们在派生列表中出现的顺序从左向右依次构造。
合成的拷贝和移动构造函数按照完全相同的顺序执行,合成的赋值运算符中的成员也按照该顺序赋值。和往常一样,对象的销毁顺序与构造顺序正好相反。