第十八章-多重继承和虚继承

  • 以动物园中动物的层次关系举例。定义一个抽象类ZooAnimal来保存动物园中动物共有的信息并提供公共接口,再定义一些辅助类,比如濒临灭绝的动物panda,由Bear和Endangered共同派生来:
class Bear : public ZooAnimal {};
class Panda : public Bear, public Endangered {};
  • 构造一个派生类的对象将同时构造并初始化它的所有基类子对象
//显式初始化所有基类
Panda::Panda(string name, bool onExhibit) :
	Bear(name,noExhibit, "Panda"), Endangered(Endangered::criticle) {}
//隐式使用Bear的默认构造函数
Panda::Panda() :
	Endangered(Endangered::criticle) {}
  • 基类的构造顺序和派生列表中基类的出现顺序保持一致,而和派生类构造函数初始值列表中基类的顺序无关。比如panda是先初始化ZooAnimal,再初始化第一个直接基类Bear,接着处理第二个直接基类Endangered,最后初始化panda
  • C++11可以让派生类从一个或多个基类中继承构造函数,但是从多个基类中继承相同的构造函数(形参列表一致)会出错
struct Base1
{
	Base1() = default;
	Base1(const string&);
	Base1(shared_ptr<int>);
};
struct Base2
{
	Base2() = default;
	Base2(const string&);
	Base2(int);
};
struct D1 : public Base1, public Base2
{
	using Base1::Base1;
	using Base2::Base2;
	//如果一个类从多个基类中继承相同的构造函数,则必须为该构造函数定义自己的版本
	//以下两个要定义自己的
	D2(const string &s) : Base1(s), Base2(s) {}
	D2() = default;
}	
  • 析构函数的调用顺序和构造函数相反,这里例子总的顺序是~Panda -> ~Endangered -> ~Bear -> ~ZooAnimal。
  • 和只有一个基类时一样,派生类自定义的拷贝构造和赋值运算符,也要使用基类的部分
Panda ying_yang("ying_yang");
Panda ling_ling = ying_yang;
//这里将调用Bear的拷贝构造函数,而Bear执行拷贝之前又会调用ZooAnimal的拷贝构造函数
//Bear部分构造完成后接着调用Endangered的拷贝构造函数,最后再执行Panda的拷贝构造函数
  • 只有一个基类的情况下,派生类的指针或引用能自动转换成一个可访问基类的指针或引用。多个基类的情况也一样,我们可以令某个可访问基类的指针或引用直接指向一个派生类对象。但是编译器不会在派生类向基类的几种转换中进行比较和选择,因为他觉得哪个基类都行
void print(const Bear&);
void print(const Endangered&);

Panda ying_yang("ying_yang");
print(ying_yang); //发生二义性错误,需要使用前缀限定符进行调用

基于指针类型或引用类型的查找

  • 如果使用ZooAnimal指针,则只有定义在ZooAnimal中的操作是可见的,Bear类型的指针或引用只能访问Bear及ZooAnimal成员。一个Endangered的指针或引用只能访问Endangered成员。当我们用Endangered指针或引用访问一个Panda对象时,Panda接口中Panda自己的部分以及属于Bear的部分都是不可见的。

多重继承下的类作用域

  • 只有一个基类的时候,派生类的作用域嵌套在直接基类和间接基类的作用域中,查找过程沿着继承体系自底向上进行,派生类的名字会隐藏基类的同名成员但是多重继承下不仅会在自身查找,还同时在所有直接基类上找,这时的问题就是如果同一个名字在多个基类中找到,就会出现二义性问题
  • 当一个类有多个基类时,有可能出现派生类从两个或更多基类中继承了同名成员的情况。此时不加前缀限定符使用会引发二义性
//假设ZooAnimal和Endangered都定义了max_weight成员,然后Panda没有定义它
double d = ying_yang.max_weight(); //那么这个调用会出错
//派生过程继承两个max_weight是合法的,只要不调用就不会出现二义性问题
//如果假设作用域运算符ZooAnimal::max_weight()或Endangered::max_weight()就可以避免
  • 还有几种会发生错误的情况:
    1. 派生类继承的两个函数形参列表不同
    2. max_weight在一个类中是私有,在另一个类中是public或protected的也会错误
  • 解决上面问题的最好办法是在Panda中定义一个max_weight

虚继承

  • 派生列表一个基类只能出现一次,但有些情况会间接继承一个类多次,比如通过两个直接基类分别继承同一个间接基类。默认情况下派生类含有继承链上每个类对应的子部分,所以如果某个类在派生过程中出现了多次,则派生类将包含该类的多个子对象
  • 上述重复继承有时候会出问题,C++通过虚继承解决该问题,共享的基类子对象成为虚基类。虚继承让基类不管出现几次,都只包含唯一一个共享的虚基类子对象
    在这里插入图片描述
  • 必须在虚派生的真实需求出现前就完成对虚派生的操作。如上图所示,只有BC两个都是从A虚派生得到,那么D才只会有A的一个成员。换句话说,虚派生只会影响从指定虚基类的派生类中进一步派生出来的类,不会影响派生类本身
//vitual和public顺序随意
class B : virtual public A {};
class C : public virtual A {};

class D : public B, public C {};

虚基类成员的可见性

  • 注意一个问题:假设类B定义了一个成员x,D1和D2都虚继承了B,D再继承D1和D2。在D作用域中,x通过D的两个基类都是可见的。如果通过D的对象使用x,有3种情况:
    1. D1和D2都没有x的定义,则x会被解析为B的成员,这时候没有二义性,一个D只有一个x
    2. x如果也是D1或D2的某个成员,那也没有二义性,因为派生类的x优先级比虚基类的x优先级高
    3. 如果D1和D2都有x,那直接访问x就会有二义性错误。解决这个问题还是老办法:定义派生类自己的x

构造函数和虚继承

  • 在虚派生中,虚基类是由最底层的派生类初始化的。之前那张图上就是D单独控制A的初始化。如果不这么规定,B和C都会初始化一遍A的部分
  • “最底层”并不是真正意义上的的最底下的派生类,继承体系中的每个类都可能成为“最底层”的类,只要我们能创建虚基类的派生类对象,这个派生类的构造函数就必须初始化它的虚基类部分。比如创建B对象时,这个B其实就是最底层,需要通过B的构造函数初始化A部分;而创建D对象时,D才是最底层,现在由D直接初始化A部分(尽管A不是D的直接基类)
D::D(a, b)
: A(a, b),
  B(a, b),
  C(a, b),
  xx(false) {}

虚继承的对象的构造方式

  • 含有虚基类的对象的构造顺序和一般的顺序不同,首先用提供给最底层派生类构造函数的初始值初始化该对象的虚基类部分,接下来按照直接基类在派生列表中出现的次序依次初始化
//class Panda : public Bear, public Raccoon, public Endangered
Panda::Panda(string name, bool onExhibit) :
	ZooAnimal(name, onExhibit, "Panda"),
	Bear(name, onExhibit),
	Raccoon(name, onExhibit),
	Endangered(Endangered::critical),
	sleeping_flag(false) {} //最后是Panda自己的部分
//虚基类总是先于非虚基类构造的,和它们在继承体系中的次序位置无关

构造函数与析构函数的次序

  • 一个类可以有多个虚基类,这时候虚的子对象按照它们在派生列表中出现的顺序从左向右依次构造
class Character {};
class BookCharacter : public Character {];
class ToyAnimal {};
class TeddyBear : public BookCharacter, public Bear, public virtual ToyAnimal {};
  • 这么多继承该怎么看构造函数的调用顺序呢?编译器会先在继承列表中找是否有虚基类,按声明顺序构造虚基类,然后按声明顺序构造基类。所以创建一个TeddyBear会依次调用下面这些构造函数
ZooAnimal(); //Bear的虚基类
ToyAnimal(); //直接虚基类
Character(); //第一个非虚的间接基类
BookCharacter(); //第一个直接非虚基类
Bear(); //第二个直接非虚基类
TeddyBear(); //自己
  • 销毁顺序和构造顺序相反,先销毁TeddyBear,再销毁ZooAnimal
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值