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;
    }
    編譯結果(MinGW2.05):
    Compiling source file(s)…
    main.cpp
    main.cpp: In function `int main()':
    main.cpp:24: error: request for member `print' is ambiguous
    main.cpp:13: error: candidates are: void Base2::print(int)
    main.cpp:6: error:                 void Base1::print()
    main.cpp:25: error: request for member `display' is ambiguous
    main.cpp:16: error: candidates are: void Base2::display(int)
    main.cpp:7: error:                 void Base1::display()
    main.cpp:26: error: request for member `show' is ambiguous
    main.cpp:14: error: candidates are: void Base2::show(int)
    main.cpp:8: error:                 void Base1::show()
    Test.exe - 9 error(s), 0 warning(s)
    解決這種二義性的方法可以是通過指定使用哪個類的版本(即帶上類名前綴)來解決。但最好的方法是,在解決二義性的派生類中定義函數的一個版本。
    虛繼承
    在標準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 …
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章