在虚派生中,虚基类是由最底层的派生类初始化的。以我们的程序为例,当创建Panda对象时,构造函数独立控制ZooAnimal的初始化过程。
为了理解这一规则,我们不妨假设当以普通规则处理初始化任务时会发生什么情况。在此例中,虚基类将会在多条继承路径上重复初始化。以ZooAnimal为例,如果应用普通规则,则Raccoon和Bear都会试图初始化Panda对象的ZooAnimal部分。
当然,继承体系中每个类都有可能在某个时刻成为“最底层的派生类”。只要我们能创建最底层类的派生类对象,该派生类的构造函数就必须初始化它的虚基类。例如,在我们的继承体系中,当创建一个Bear(或Raccoon)的对象时,它已经处于派生类的最底层,因为Bear(或Raccoon)的构造函数将直接初始化其ZooAnimal基类部分:
Bear::Bear(std::string name, bool onExhibit) : ZooAnimal(name, onExhibit, "Bear") {} Raccoon::Raccoon(std::string name, bool onExhibit) : ZooAnimal(name, onExhibit, "Raccoon") {}
而当创建一个Panda对象时,Panda位于派生的最底层并由它负责初始化共享的ZooAnimal的基类部分。即使ZooAnimal不是Panda的直接基类,Panda的构造函数也可以初始化ZooAnimal:
Panda::Panda(std::string name, bool onExhibit) : ZooAnimal(name, onExhibit, "Panda"), Bear(name, onExhibit), Raccoon(name, onExhibit), Endangered(Endangered::critical), sleeping_flag(false) {}
虚继承的对象的构造方式
含有虚基类的对象的构造顺序与一般的顺序稍有区别:首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生类表中出现的次序依次对其进行初始化。
例如,当我们创建Panda对象时:
- 首先使用Panda的构造函数初始值列表中提供的初始值构造基类ZooAnimal部分。
- 接下来构造Bear部分。
- 然后构造Raccoon部分。
- 然后构造第三个直接基类Endangered。
- 最后构造Panda。
如果Panda没有显示地初始化ZooAnimal基类,则ZooAnimal的默认构造函数将被调用。如果ZooAnimal没有默认构造函数,则代码将发生错误。
构造函数与析构函数的次序
一个类可以有多个虚基类。此时,这些虚的子对象将按照它们在派生列表中出现的顺序从左向右依次构造。例如,下面这个稍显杂乱的TeddyBear派生关系中有两个虚基类:ToyAnimal是直接虚基类,ZooAnimal是Bear的虚基类:
class Character {/* ... */}; class BookChracter : public Character {/* ... */}; class ToyAnimal {/* ... */}; class TeddyBear : public BookChracter, public Bear, public virtual ToyAnimal {/* ... */};
编译器将按照直接基类的声明顺序对其依次进行检查,以确定其中是否含有虚基类。如果有,则先构造虚基类,然后按照声明的顺序逐一构造其它非虚基类。因此,要想创建一个TeddyBear对象,选哟按照如下次序调用这些构造函数:
ZooAnimal(); //Bear的虚基类 ToyAnimal(); //直接虚基类 Character(); //第一个非虚基类的间接基类 BookCharacter(); //第一个直接非虚基类 Bear(); //第二个直接非虚基类 TeddyBear(); //最底层的派生类
合成的拷贝和移动构造函数按照完全相同的顺序执行,和成的赋值运算符中的成员也按照该顺序赋值。和往常一样,对象的销毁顺序与构造顺序正好相反,首先销毁TeddyBear部分,最后销毁ZooAnimal部分。