C++學習筆記十九-多重繼承與虛繼承


概述:多重繼承是從多於一個直接基類派生類的能力,多重繼承的派生類繼承其所有父類的屬性。

一、多重繼承的定義

     1. 由逗號分隔的基類列表:

    class Panda : public Bear, public Endangered {
    };

             派生類為每個基類(顯式或隱式地)指定了訪問級別——publicprotectedprivate。像單繼承一樣,只有在定義之后,類才可以用作多重繼承的基類。對於類可以繼承的基類的數目,沒有語言強加強加的限制,但在一個給定派生列表中,一個基類只能出現一次。

     2.多重繼承的派生類從每個基類中繼承狀態:在多重繼承下,派生類的對象包含每個基類的基類子對象。

     3.派生類構造函數初始化所有基類:構造派生類型的對象包括構造和初始化所有基類子對象。像繼承單個基類的情況一樣,派生類的構造函數可以在構造函數初始化式中給零個或多個基類傳遞值。多重繼承中若沒有顯式調用某個基類的構造函數,則編譯器會調用該基類默認構造函數

     4.構造的次序:構造函數初始化式只能控制用於初始化基類的值,不能控制基類的構造次序。基類構造函數按照基類構造函數在類派生列表中的出現次序調用。

    5.構造函數調用次序既不受構造函數初始化列表中出現的基類的影響,也不受基類在構造函數初始化列表中的出現次序的影響。

6.析構的次序:總是按構造函數運行的逆序調用析構函數。

 

二、轉換與多個基類

     1.在單個基類情況下,派生類的指針或引用可以自動轉換為基類的指針或引用,對於多重繼承也是如此,派生類的指針或引用可以轉換為其任意其類的指針或引用。

     2.在多重繼承情況下,遇到二義性轉換的可能性更大。編譯器不會試圖根據派生類轉換來區別基類間的轉換,轉換到每個基類都一樣好。例如,如果有 print 函數的重載版本:

    void print(const Bear&);
    void print(const Endangered&);
三、多重繼承下的虛函數
   1.像單繼承一樣,用基類的指針或引用只能訪問基類中定義(或繼承)的成員,不能訪問派生類中引入的成員。
 2.當一個類繼承於多個基類的時候,那些基類之間沒有隱含的關系,不允許使用一個基類的指針訪問其他基類的成員。
 3.確定使用哪個虛析構函數:假定所有根基類都將它們的析構函數適當定義為虛函數,那么,無論通過哪種指針類型刪除對象,虛析構函數的處理都是一致的。
 
四、多重繼承派生類的復制控制:
   1.多重繼承的派生類的逐個成員初始化、賦值和析構,表現得與單繼承下的一樣,使用基類自己的復制構造函數、賦值操作符或析構函數隱式構造、賦值或撤銷每個基類。
  2.像單繼承的情況一樣,如果具有多個基類的類定義了自己的析構函數,該析構函數只負責清除派生類。如果派生類定義了自己的復制構造函數或賦值操作符,則類負責復制(賦值)所有的基類子部分。只有派生類使用復制構造函數或賦值操作符的合成版本,才自動復制或賦值基類部分。
 
五、多重繼承下的類作用域:
   1.當一個類有多個基類的時候,通過所有直接基類同時進行名字查找。多重繼承的派生類有可能從兩個或多個基類繼承同名成員,對該成員不加限定的使用是二義性的。
 2.即使兩個繼承的函數有不同的形參表,也會產生錯誤。類似地,即使函數在一個類中是私有的而在另一個類中是公用或受保護的,也是錯誤的。
   3.避免用戶二義性:可以通過指定使用哪個類解決二義性:
    ying_yang.Endangered::print(cout);

     4.避免潛在二義性最好的方法是,在解決二義性的派生類中定義函數的一個版本。例如,應該給選擇使用哪個 print 版本的 Panda 類一個 print 函數:

    std::ostream& Panda::print(std::ostream &os) const

    {
        Bear::print(os);        // print the Bear part
        Endangered::print(os);  // print the Endangered part
        return os;
    }
 
六、虛繼承:在多重繼承下,一個基類可以在派生層次中出現多次。實際上,我們的程序已經使用過通過繼承層次多次繼承同一基類的類。

      1.在 C++ 中,通過使用虛繼承解決這類問題。虛繼承是一種機制,類通過虛繼承指出它希望共享其虛基類的狀態。在虛繼承下,對給定虛基類,無論該類在派生層次中作為虛基類出現多少次,只繼承一個共享的基類子對象。共享的基類子對象稱為虛基類

      2.istreamostream 類對它們的基類進行虛繼承。通過使基類成為虛基類istreamostream 指定,如果其他類(如 iostream 同時繼承它們兩個,則派生類中只出現它們的公共基類的一個副本。通過在派生列表中包含關鍵字 virtual 設置虛基類:

    class istream : public virtual ios { ... };
    class ostream : virtual public ios { ... };
    // iostream inherits only one copy of its ios base class
    class iostream: public istream, public ostream { ... };
  3.虛基類的聲明:通過用關鍵字 virtual 修改聲明,將基類指定為通過虛繼承派生。例如,下面的聲明使 ZooAnimal 類成為 Bear 類和 Raccoon 類的虛基類:
    // the order of the keywords public and virtual is not significant
    class Raccoon : public virtual ZooAnimal { /* ... */ };
    class Bear : virtual public ZooAnimal { /* ... */ };
  4.指定虛派生只影響從指定了虛基類的類派生的類。除了影響派生類自己的對象之外,它也是關於派生類與自己的未來派生類的關系的一個陳述。virtual 說明符陳述了在后代派生類中共享指定基類的單個實例的願望。
  5.任何可被指定為基類的類也可以被指定為虛基類,虛基類可以包含通常由非虛基類支持的任意類元素。
  6.支持到基類的常規轉換:即使基類是虛基類,也照常可以通過基類類型的指針或引用操縱派生類的對象。
 
七、虛基類成員的可見性

    1. 假定通過多個派生路徑繼承名為 X 的成員,有下面三種可能性:

      a. 如果在每個路徑中 X 表示同一虛基類成員,則沒有二義性,因為共享該成員的單個實例。

       b.如果在某個路徑中 X 是虛基類的成員,而在另一路徑中 X 是后代派生類的成員,也沒有二義性——特定派生類實例的優先級高於共享虛基類實例

       c. 如果沿每個繼承路徑 X 表示后代派生類的不同成員,則該成員的直接訪問是二義性的。

     像非虛多重繼承層次一樣,這種二義性最好用在派生類中提供覆蓋實例的類來解決。

 

八、特殊的初始化語義

    1.通常,每個類只初始化自己的直接基類。在應用於虛基類的進修,這個初始化策略會失敗。如果使用常規規則,就可能會多次初始化虛基類。類將沿着包含該虛基類的每個繼承路徑初始化。為了解決這個重復初始化問題,從具有虛基類的類繼承的類對初始化進行特殊處理。在虛派生中,由最低層派生類的構造函數初始化虛基類。

   2.雖然由最低層派生類初始化虛基類,但是任何直接或間接繼承虛基類的類一般也必須為該基類提供自己的初始化式。只要可以創建虛基類派生類類型的獨立對象,該類就必須初始化自己的虛基類,這些初始化式只有創建中間類型的對象時使用。

 

九、怎樣構造虛繼承的對象

    1.無論虛基類出現在繼承層次中任何地方,總是在構造非虛基類之前構造虛基類。

a.按聲明次序檢查直接基類,確定是否存在虛基類。一旦構造了虛基類,就按聲明次序調用非虛基類的構造函數.

b.在合成復制構造函數中使用同樣的構造次序,在合成賦值操作符中也是按這個次序給基類賦值。保證調用基類析構函數的次序與構造函數的調用次序相反。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM