C++ Primer 学习笔记_95_用于大型程序的工具 --多重继承与虚继承
用于大型程序的工具
--多重继承与虚继承
引言:
大多数应用程序使用单个基类的公用继承,但是,在某些情况下,单继承是不够用的,因为可能无法为问题域建模,或者会对模型带来不必要的复杂性。
在这些情况下,多重继承可以更直接地为应用程序建模。多重继承是从多于一个直接基类派生类的能力,多重继承的派生类继承其所有父类的属性。
一、多重继承
1、定义多个类
为了支持多重继承,扩充派生列表:
class Bear : public ZooAnimal { //... };
以支持由逗号分隔的基类列表:
class Panda : public Bear,public Endangered { //... };
派生类为每个基类(显式或隐式地)指定了访问级别——public、protected 或 private。像单继承一样,只有在定义之后,类才可以用作多重继承的基类。对于类可以继承的基类的数目,没有语言强加的限制,但在一个给定派生列表中,一个基类只能出现一次。
2、多重继承的派生类从每个基类中继承状态
在多重继承下,派生类的对象包含每个基类的基类子对象:
Panda ying_ying("ying_ying");
对象ying_yang包含一个 Bear类子对象(Bear类子对象本身包含一个ZooAnimal基类子对象)、一个Endangered类子对象以及Panda类中声明的非static数据成员(如果有的话)。
3、派生类构造函数初始化所有基类
构造派生类型的对象包含构造和初始化它的所有基类子对象。像继承单个基类的情况一样,派生类的构造函数可以在构造函数初始化式中给零个或多个基类传递值:
Panda::Panda(std::string name, bool onExhibit): Bear(name, onExhibit, "Panda"), Endangered(Endangered::critical) {} Panda::Panda(): Endangered(Endangered::critical) {}
4、构造次序
构造函数初始化式只能控制用于初始化基类的值,不能控制基类的构造次序。基类构造函数按照基类构造函数在类派生列表中的出现次序调用。对Panda而言,基类初始化的次序是:
1)ZooAnimal,从Panda的直接基类Bear沿层次向上的最终基类。
2)Bear,第一个直接基类。
3)Endangered,第二个直接基类,它本身没有基类。
4)Panda,初始化Panda本身的成员,然后运行它的构造函数的函数体。
【注解】
构造函数调用次序既不受构造函数初始化列表中出现的基类的影响,也不受基类在构造函数初始化列表中出现的次序的影响。
例如,在Panda类的默认构造函数中,隐式调用Bear类的默认构造函数,它不出现在构造函数初始化列表中,但仍在显式列出的Endangered类构造函数之前调用Bear类的默认构造函数。
5、析构的次序
总是按构造函数运行的逆序调用析构函数。如:
~Panda, ~Endangered, ~Bear, ~ZooAnimal。
二、转换与多个基类
在单个基类情况下,派生类的指针或引用可以自动转换为基类的指针或引用,对于多重继承也是如此,派生类的指针或引用可以转换为其任意基类的指针或引用。
void print(const Bear &); void highlight(const Endangered &); ostream &operator<<(ostream &,const ZooAnimal &); Panda ying_ying("ying_ying"); print(ying_ying); highlight(ying_ying); cout << ying_ying << endl;
在多重继承的情况下,遇到二义性转换的可能性更大。编译器不会试图根据派生类转换来区别基类间的转换,转换到每个基类都一样好:
void print(const Bear &); void print(const Endangered &); Panda ying_ying("ying_ying"); print(ying_ying); //Error:ambiguous
//P617 习题17.25 class X { //... }; class A { //... }; class B : public A { //... }; class C : private B { //... }; class D : public X ,public C { //... }; D *pd = new D; X *px = pd; B *pb = pd; //Error:注意C对B的继承是private! A *pa = pd; //Error C *pc = pd;
1、多重继承下的虚函数
假定我们的类定义了下表列出的虚成员:
ZooAnimal/Endangered类中的虚函数 | |
---|---|
函数 | 定义自己版本的类 |
ZooAnimal::ZooAnimal | |
Bear::Bear | |
Endangered::Endangered | |
Panda::Panda | |
highlight | Endangered::Endangered |
Panda::Panda | |
toes | Bear::Bear |
Panda::Panda | |
cuddle | Panda::Panda |
析构函数 | ZooAnimal::ZooAnimal |
Endangered::Endangered |
2、基于指针类型或引用类型的查找
像单继承一样,用基类的指针或引用只能访问基类中定义(或继承)的成员,不能访问派生类中引入的成员。
当一个类继承于多个基类的时候,那些基类之间没有隐含的关系,不允许使用一个基类的指针访问其他基类的成员。
Bear *pb = new Panda("ying_yang"); pb->print(cout); // ok:参考上表 pb->cuddle(); // error pb->highlight(); // error delete pb; // ok
在通过Endangered指针或引用访问Panda对象时,不能访问Panda接口的Panda特定的部分和Bear部分:
Endangered *pe = new Panda("ying_yang"); pe->print(cout); // ok pe->toes(); // error pe->cuddle(); // error pe->highlight(); // ok delete pe; // ok
3、确定使用哪个虚析构函数
假定所有根基类都将它们的析构函数适当定义为虚函数,那么,无论通过哪种指针类型删除对象,虚析构函数的处理都是一致的:
//每个指针都指向Panda对象 delete pz; delete pb; delete pp; delete pe;
假定这些指针每个都向Panda对象,则每种情况下发生完全相同的析构函数调用次序。析构函数调用的次序是构造函数次序的逆序:通过虚机制调用Panda析构函数。随着Panda析构函数的执行,依次调用Endangered、Bear和ZooAnimal的析构函数。
三、多重继承派生类的复制控制
多重继承的派生类的逐个成员初始化、赋值和析构,表现得与单继承下的一样,使用基类自己的复制构造函数、赋值操作符或析构函数隐式构造、赋值或撤销每个基类。
假定Panda类使用默认复制控制成员。ling_ling的初始化
Panda ying_yang("ying_yang"); Panda ling_ling = ying_yang;
使用默认复制构造函数调用Bear复制构造函数,Bear复制构造函数依次在执行 Bear复制构造函数之前运行ZooAnimal复制构造函数。一旦构造了ling_ling的 Bear部分,就运行Endangered复制构造函数来创建对象的那个部分。最后,运行Panda复制构造函数。
合成的赋值操作符的行为类似于复制构造函数。
合成的析构函数撤销Panda对象的每个成员,并且按构造次序的逆序为基类部分调用析构函数。
【小心地雷】
像单继承的情况一样,如果具有多个基类的类定义了自己的析构函数,该析构函数只负责清除派生类。如果派生类定义了自己的复制构造函数或赋值操作符,则类负责复制(赋值)所有的基类子部分。只有派生类使用复制构造函数或赋值操作符的合成版本,才自动复制或赋值基类部分。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。