C++中虚拟继承
多重继承
在多重继承中,基类的构造函数的调用次序即不受派生类构造函数初始化列表中出现的基类构造函数的影响,也不受基类在构造函数初始化列表中的出现次序的影响,它按照基类在类派生列表中的出现次序依次调用相应的基类构造函数。析构函数与构造顺序逆序进行。
多重继承中,派生类的指针或引用可以转换为其任意基类的指针或者引用、因此,这种转换更可能遇到二义性的问题。
在多重继承中,成员函数中使用的名字查找首先在函数本身进行,如果不能在本地找到名字,就继续在成员的类中查找,然后同时(并行)查找所有基类继承子树。多重继承的派生类有可能从两个或者多个基类继承同名成员,对该成员不加限定的使用是二义性。
注意:多重继承中首先发生名字查找。你可能会感到吃惊的是,即使两个继承的同名函数有不同的形参表,也会产生错误。类似地,即使函数在一个类中是私有的而在另一个类中是公有或者受保护的,也是错误的。或者在一个类给定义了函数,而在另一个类中没有定义,调用仍是错误的。
下面给出实例:
#include <iostream> class Base1 { public: void print() {} void display() {} void show() {} }; class Base2 { public: void print(int) {} void show(int); private: void display(int) {} }; class Derived: public Base1, public Base2 { }; int main () { Derived d; d.print(); d.display(); d.show(); return 0; }
编辑结果错误,这是因为程序中函数print()造成了二义性,程序不知道调用哪一个。解决这种二义性的方法可以是通过制定使用哪个类的版本(即带上类名的前缀)来解决。但最好的解决方法是在解决二义性的派生类中定义函数的一个版本。
虚继承
在多继承中,例如有四个类,类A、B、C、D,其中类B和类C继承类A,类D分别继承类B和类C。这里如果类D采用常规继承,则每个类D对象可能包含两个类A子对象,这个是我们不希望出现的程序希望类B和类C可以共享一个类A对象。
在C++中通过使用虚继承来解决这类问题、虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。
通过在派生类列表中包含关键字virtual设置虚基类,例如:
class istream : public virtual ios {…}; class ostream : virtual public ios {…}; class iostream : public istream, public ostream {…};
假定通过对个派生路劲继承名为X的成员,有下面三种可能性:
1)如果在每个路径中X表示同一虚基类成员,则没有二义性,因为共享该成员的单个实例;
2)如果在某个路劲中X是虚基类的成员,而另一路劲中X是后代派生类的成员,也没有二义性,因为特定派生类实例的优先级高于共享虚基类实例。
3)如果沿每个继承路劲X表示后代派生类的不同成员,则该成员的直接访问是二义性的。
例如:
#include <iostream> class B { public: void print() { std::cout 《 "B" 《 std::endl; } }; class D1: public virtual B { }; class D2: public virtual B { public: void print() { std::cout 《 "D2" 《 std::endl; } }; class DD: public D1, public D2 { }; int main () { DD d; d.print(); // ok: call D2::print return 0; }
特殊的初始化语义
通常,每个类只初始化自己的直接基类。在应用与虚基类的时候,这个初始化策略失败。如果使用常规规则,就可能会多次初始化虚基类。类将沿着包含该锡基雷的每个继承路劲初始化。
为了解决这个重复的初始化问题,从具有虚基类的类继承的类对初始化进行特殊处理。在虚派生中,由最底层派生类的构造函数初始化虚基类。
虽然最底层派生类初始化虚基类,但是任何直接或者间接继承虚基类的类一般也必须为该基类提供自己的初始化式。只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类,这些初始化式只在创建中间类型的对象时使用。
例如,我们四个类:ZooAnimal, Bear, Raccoon和Panda,它们之间构造一个继承层次:Bear和Raccoon继承ZooAnimal,Panda继承Bear和Raccoon.那么,它们的构造函数就形如:
Bear::Bear(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Bear") {}
Raccoon::Raccoon(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Raccoon") {}
Panda::Panda(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Panda"), Bear(name, onExhibit), Raccoon(name, onExhibit) {}
当创建Panda对象的时候,构造过程如下:
1)首先使用构造函数初始化列表中指定的初始化式构造ZooAnima部分;
2)接下来,构造Bear部分(在派生列表中Bear在前)。忽略Bear初始化列表中用于ZooAnima构造函数的初始化式;
3)然后,构造Raccoon部分,再次忽略ZooAnima初始化式;
4)最后,构造Panda部分。
如果Panda构造函数不是显式初始化ZooAnima基类,就使用ZooAnima默认构造函数;如果ZooAnima没有默认构造函数,则代码出错。
无论虚基类出现在继承层次中的任何地方,总是在构造非虚基类之前构造虚基类。
例如:有下面的继承关系:
class Character { };
class BookCharater: public Character { };
class ToyAnimal { };
class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal { };
按声明次序检查直接基类,确定是否存在虚基类。上例中,首先检查BookCharater的继承子树,然后检查Bear的继承子树,最后检查ToyAnimal的继承子树。按照从根类开始向下到最底层派生类的次序检查每个子树。在这里一次检查到ZooAnimal和ToyAnimal为虚基类。
TeddyBear的虚基类的构造次序是先ZooAnimal再ToyAnimal(检查的顺序)。一旦构造了虚基类,就按照声明次序调用非虚基类的构造函数:首先是BookCharacter,它导致调用Character构造函数,然后是Bear。在这里,由最底层派生类TeddBear指定用于ZooAnimal和ToyAnimal的初始化。
当然,对于析构函数的调用顺序与构造函数相反。
示例代码如下:
#include <iostream> class Character { public: Character() { std::cout 《 "Character Constructor" 《 std::endl; } ~Character() { std::cout 《 "Character Destructor" 《 std::endl; } }; class BookCharacter: public Character { public: BookCharacter() { std::cout 《 "BookCharacter Constructor" 《 std::endl; } ~BookCharacter() { std::cout 《 "BookCharacter Destructor" 《 std::endl; } }; class ZooAnimal { public: ZooAnimal() { std::cout 《 "ZooAnimal Constructor" 《 std::endl; } ~ZooAnimal() { std::cout 《 "ZooAnimal Destructor" 《 std::endl; } }; class Bear: public virtual ZooAnimal { public: Bear() { std::cout 《 "Bear Constructor" 《 std::endl; } ~Bear() { std::cout 《 "Bear Destructor" 《 std::endl; } }; class ToyAnimal { public: ToyAnimal() { std::cout 《 "ToyAnimal Constructor" 《 std::endl; } ~ToyAnimal() { std::cout 《 "ToyAnimal Destructor" 《 std::endl; } }; class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal { public: TeddyBear() { std::cout 《 "TeddyBear Constructor" 《 std::endl; } ~TeddyBear() { std::cout 《 "TeddyBear Destructor" 《 std::endl; } }; int main () { TeddyBear tb; return 0; }
运行结果:
ToyAnimal Constructor
Character Constructor
BookCharacter Constructor
Bear Constructor
TeddyBear Constructor
TeddyBear Destructor
Bear Destructor
BookCharacter Destructor
Character Destructor
ToyAnimal Destructor
ZooAnimal Destructor
Terminated with return code 0
Press any key to continue …