高质量程序设计指南c++/c语言(36)--虚继承
先看虚虚继承的一个例子:ios是抽象基类,ostream和istream都虚继承自ios,而iostream又继承自ostream和istream,这样,ios在iostream中只有一份实例。
#include<iostream> using namespace std; class Root { public: Root() { cout << "Root()" << endl; } }; class Level11: public virtual Root { public: Level11() { cout << "Level11()" << endl; } }; class Level12: public virtual Root { public: Level12() { cout << "Level12()" << endl; } }; class Level21:public Level11, public Level12 { public: Level21() { cout << "Level21()" << endl; } }; int main(void) { Level21 level; return 0; }
输出:
Root()
Level11()
Level12()
Level21()
上面只输出一次Root(),是因为虚基类的特殊的初始化。通常,每个类只初始化自己的直接基类,在应用于虚基类的时候,这个初始化策略就会失败。如果使用常规规则,就可能会多次初始化虚基类。类将沿着包含该虚基类的每个继承路径初始化。为了解决这个重复初始化问题,由最底层派生类的构造函数初始化虚基类。在上面的例子中,在创建Level21对象的时候,由Level21的构造函数来初始化Root类。当然,在创建Level12的时候,就由Level12的构造函数来初始化Root类。
#include<iostream> using namespace std; class Root { public: Root() { cout << "Root()" << endl; } Root(int data) { cout << "Root(int data)" << endl; } int rootdData; }; class Level11: public virtual Root { public: Level11(int data):Root(data) { cout << "Level11(int data)" << endl; } int level11Data; }; class Level12: public virtual Root { public: Level12(int data):Root(data) { cout << "Level12(int data)" << endl; } int level12Data; }; class Level21:public Level11, public Level12 { public: Level21(int data):Root(data), Level11(data), Level12(data) { cout << "Level21(int data):Root(data), Level11(data), Level12(data)" << endl; } Level21():Level11(10), Level12(10) //与Level21():Root(), Level11(10), Level12(10)等价 { cout << "Level21():Level11(data), Level12(data)" << endl; } int level21Data; }; int main(void) { Level21 a; cout << endl; Level21 b(100); return 0; } 输出: Root() Level11(int data) Level12(int data) Level21():Level11(data), Level12(data) Root(int data) Level11(int data) Level12(int data) Level21(int data):Root(data), Level11(data), Level12(data)
1、构造函数与析构函数次序
无论虚基类出现在继承层次中的任何地方,总是在构造非虚基类之前构造虚基类。看下面毫无规律的TeddyBear:
class Character{}; class BookCharacter: public Character{}; class ZooAnimal{}; class Bear:public virtual ZooAnimal{}; class ToyAnimal{}; class TeddyBear:public BookCharacter, public Bear, public virtual ToyAnimal{};
当构造TeddyBear类的对象时,按声明次序检查直接基类,确定是否存在虚基类。例如,首先检查BookCharacter的继承子树,然后检查Bear的继承子树,最后检查ToyAnimal的继承子树。创建TeddyBear类的对象,其构造函数调用顺序如下:
1\ ZooAnimal()
2\ ToyAnimal
3\ Character
4\ BookCharacter
5\ TeddyBear
在这里,由最低派生类TeddyBear控制ZooAnimal和ToyAnimal的初始化。
在合成赋值运算符中也是按照这个顺序赋值。保证调用基类析构函数的次序与构造函数的调用次序正好相反。
2、虚基类成员的可见性
#include<iostream> using namespace std; class Root { public: Root() { cout << "Root()" << endl; x = 0; } int x; }; class Level11: public virtual Root { public: Level11() { cout << "Level11()" << endl; } }; class Level12: public virtual Root { public: Level12() { cout << "Level12()" << endl; x = 12; } int x; }; class Level21:public Level11, public Level12 { public: Level21() { cout << "Level21()" << endl; } }; int main(void) { Level21 level; level.x = 10; cout << level.Root::x << endl; cout << level.Level12::x << endl; return 0; } 输出: Root() Level11() Level12() Level21() 0 10
可以无二义性的直接访问共享虚基类中的成员。同样,如果只沿一个派生路径重定义来自虚基类的成员,则可以直接访问该重定义成员。在非虚继承情况下,两种访问都可能是二义性的。
假定通过多个派生路径继承名为x的成员,有下面三种可能:
1、如果在每个路径中x表示同一虚基类成员,则没有二义性。
2、如果在某个路径中x是虚基类的成员,而在另一路径中x是后代派生类的成员,也没有二义性--特定派生类实例的优先级高于共享基类实例。
3、如果沿每个继承路径x表示后代派生类的不同成员,则该成员的直接访问是二义性的。
class Base { public: void bar(int){} protected: int ival; }; class Derived1: virtual public Base { public: void bar(char){} void foo(char){} protected: char cval; }; class Derived2: virtual public Base { public: void foo(int){} protected: int ival; char cval; }; class VMI: public Derived1, public Derived2 { public: void test() { cval = 'a'; //error: Derived1::cval or Derived2::cval //即使Derived1的cval变为private的,也会发生二义性错误 ival = 100; //ok: Derived2::ival bar('d'); //ok: Derived1::bar foo('d'); //error: Derived1::foo or Derived2::foo 首先发生名字查找,然后检查参数 //即使Derived2的foo变为private的,也会发生二义性错误 } };