虚继承

来源:互联网 发布:linux shell输入日志 编辑:程序博客网 时间:2024/05/07 06:01
虚继承
 
   在标准I/O库中的类都继承了一个共同的抽象基类ios,那个抽象基类管理流的条件状态并保存流所读写的缓冲区。istream和ostream类直接继承这个公共基类,库定义了另一个名为isotream的类,它同时继承istream和ostream,iostream类既可以对流进行读又可以对流进行写。如果I/O类型使用常规继承,则每个iostream对象可能包含两个ios子对象:一个包含在它的istream子对象中,另一个包含在它的 ostream子对象中。从设计角度讲,这个实现是错误的:iostream类想要对单个缓冲区进行读和写,它希望跨越输入和输出操作符共享条件状态。
 
   在C++中,通过使用虚继承(virtual inheritance)解决这类问题。虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类(virtual base class)。
 
   通过在派生类列表中包含关键字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)首先使用构造函数初始化列表中指定的初始化式构造ZooAnimal部分;
   2)接下来,构造Bear部分(在派生列表中Bear在前)。忽略Bear初始化列表中用于ZooAnimal构造函数的初始化式;
   3)然后,构造Raccoon部分,再次忽略ZooAnimal初始化式;
   4)最后,构造Panda部分。
 
   如果Panda构造函数不显式初始化ZooAnimal基类,就使用ZooAnimal默认构造函数;如果ZooAnimal没有默认构造函数,则代码出错。
 
   无论虚基类出现在继承层次中的任何地方,总是在构造非虚基类之前构造虚基类。
 
   例如,有下面的继承关系:
   class Character { };
   class BookCharater: public Character { };
   class ToyAnimal { };
   class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal { };
 
直观继承图为:
   按声明次序检查直接基类,确定是否存在虚基类。上例中,首先检查BookCharacter的继承子树,然后检查Bear的继承子树,最后检查 ToyAnimal的继承子树。按从根类开始向下到最低层派生类的次序检查每个子树。在这里依次检查到ZooAnimal和ToyAnimal为虚基类。
 
   TeddyBear的虚基类的构造次序是先ZooAnimal再ToyAnimal(检查到的顺序)。一旦构造了虚基类,就按声明次序调用非虚基类的构造函数:首先是BookCharacter,它导致调用Character构造函数,然后是Bear。在这里,由最低层派生类TeddyBear指定用于 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;
}

运行结果如下:

ZooAnimal Constructor
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 ...
0 0
原创粉丝点击