天天看點

C++ Primer 學習筆記_96_用于大型程式的工具 --多重繼承與虛繼承[續1]用于大型程式的工具

四、多重繼承下的類作用域

成員函數中使用的名字和查找首先在函數本身進行,如果不能在本地找到名字,就繼續在本類中查找,然後依次查找每個基類。在多重繼承下,查找同時檢察所有的基類繼承子樹 —— 在我們的例子中,并行查找 Endangered子樹和Bear/ZooAnimal子樹。如果在多個子樹中找到該名字,則那個名字的使用必須顯式指定使用哪個基類;否則,該名字的使用是二義性的。

【小心地雷】

當一個類有多個基類的時候,通過對所有直接基類同時進行名字查找。多重繼承的派生類有可能從兩個或多個基類繼承同名成員,對該成員不加限定的使用是二義性的。

1、多個基類可能導緻二義性

假定Bear類和 Endangered類都定義了名為print的成員,如果Panda類沒有定義該成員,則

這樣的語句将導緻編譯時錯誤。

如果每個print調用明确指出想要哪個版本 —— Bear::print還是Endangered::print,也可以避免錯誤。隻有在存在使用該成員的二義性嘗試的時候,才會出錯。如果隻在一個基類子樹中找到聲明,則辨別符得以确定而查找算法結束。例如:

可以通過編譯,名字population将隻在基類Endangered中找到,并且在Bear類或其任意基類中都不會出現。

2、首先發生名字查找

[着重了解這一段!]雖然兩個繼承的print成員的二義性相當明顯,但是也許更令人驚訝的是:

1,即使兩個繼承的函數有不同的形參表,也會産生錯誤。

2,即使函數在一個類中是私有的而在另一個類中是公用或受保護的,也是錯誤的。

3,如果在ZooAnimal類中定義了print而 Bear類中沒有定義,調用仍是錯誤的。

[釋疑]名字查找總是以兩個步驟發生:首先編譯器找到一個比對的聲明(或者,在這個例子中,找到兩個比對的聲明,這導緻二義性),然後,編譯器才确定所找到的聲明是否合法。

3、避免使用者級二義性

可以通過指定使用哪個類解決二義性:

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

五、虛繼承

【執行個體】每個 IO庫類都繼承了一個共同的抽象基類,那個抽象基類管理流的條件狀态并儲存流所讀寫的緩沖區。istream和 ostream類直接繼承這個公共基類,庫定義了另一個名為iostream的類,它同時繼承istream和ostream,iostream類既可以對流進行讀又可以對流進行寫。

C++ Primer 學習筆記_96_用于大型程式的工具 --多重繼承與虛繼承[續1]用于大型程式的工具

多重繼承的類從它的每個父類繼承狀态和動作,如果IO類 型使用正常繼承,則每個iostream對象可能包含兩個ios子對象:一個包含在它的istream子對象中,另一個包含在它的ostream子對象中,從設計角度講,這個實作正是錯誤的:iostream類想要對單個緩沖區進行讀和寫,它希望跨越輸入和輸出操作符共享條件狀态。如果有兩個單獨的ios對象,這種共享是不可能的。

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

istream和 ostream類對它們的基類進行虛繼承。通過使基類成為虛基類,istream 和ostream指定,如果其他類(如iostream)同時繼承它們兩個,則派生類中隻出現它們的公共基類的一個副本。通過在派生清單中包含關鍵字virtual設定虛基類:

一個不同的Panda類

在動物學圈子中,對于Panda是屬于 Raccoon科還是 Bear科已經争論了100年以上。因為軟體設計主要是一種服務行業,是以最現實的解決方案是從二者派生Panda:

虛繼承Panda層次如圖所示:

C++ Primer 學習筆記_96_用于大型程式的工具 --多重繼承與虛繼承[續1]用于大型程式的工具

虛繼承有一個不直覺的特征:必須在提出虛派生的任意實際需要之前進行虛派生。隻有在使用Panda的聲明時,虛繼承才是必要的,但如果 Bear類和 Raccoon類不是虛派生的,Panda類的設計者就沒有好運氣了。

實際上,中間基類指定其繼承為虛繼承的要求很少引起任何問題。通常,使用虛繼承的類層次是一次性由一個人或一個項目設計組設計的,獨立開發的類很少需要其基類中的一個是虛基類,而且新基類的開發者不能改變已經存在的層次。

六、虛基類聲明

通過用關鍵字virtual修改聲明,将基類指定為通過虛繼承派生:

指定虛派生隻影響從指定了虛基類的類派生的類。除了影響派生類自己的對象之外,它也是關于派生類與自己的派生類的關系的一個陳述。

任何可被指定為基類的類也可以被指定為虛基類,虛基類可以包含通常由非虛基類支援的任意類元素。

1、支援到基類的正常轉換

即使基類是虛基類,也照常可以通過基類類型的指針或引用操縱派生類的對象:

2、虛基類成員的可見性

使用虛基類的多重繼承層次比沒有虛繼承的引起更少的二義性問題。

【說明:】

可以無二義性地直接通路共享虛基類中的成員。同樣,如果隻沿一個派生路徑重定義來自虛基類的成員,則可以直接通路該重定義成員。在非虛派生情況下,兩種通路都可能是二義性的。

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

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

2.如果在某個路徑中X是虛基類的成員,而在另一路徑中X是後代派生類的成員,也沒有二義性 —— 特定派生類執行個體的優先級高于共享虛基類執行個體。

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

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

繼續閱讀