標籤:cal prot 聲明 error poi 傳遞值 樣本 列表 另一個
大多數應用程式使用單個基類的公用繼承,但是在某些情況下,需要從多於一個直接基類衍生類別,也就是所謂的多重繼承,多重繼承的衍生類別繼承其所有父類的屬性。
1:多重繼承的例子:
class Bear : public ZooAnimal {};class Panda : public Bear, public Endangered {};
衍生類別為每個基類(顯式或隱式地)指定了存取層級——public、protected 或private。
2:在多重繼承下,衍生類別的對象包含每個基類的基類子物件。當構造一個Panda對象的時候,該對象包含一個 Bear 類子物件(Bear 類子物件本身包含一個 ZooAnimal 基類子物件)、一個 Endangered 類子物件以及 Panda 類中聲明的非 static 資料成員(如果有的話),如:
3:構造衍生類別的對象包括構造和初始化所有基類子物件。衍生類別的建構函式可以在建構函式初始化式中給零個或多個基類傳遞值:
// explicitly initialize both base classesPanda::Panda(std::string name, bool onExhibit): Bear(name, onExhibit, "Panda"),Endangered(Endangered::critical) { }// implicitly use Bear default constructor to initialize the Bear subobjectPanda::Panda(): Endangered(Endangered::critical) { }
建構函式初始化式只能控制用於初始化基類的值,不能控制基類的構造次序。基類建構函式按照基類在類衍生的資料行表中的出現次序調用。對 Panda 而言,基類初始化的次序是:
a. ZooAnimal,從 Panda 的直接基類 Bear 沿層次向上的最終基類。
b. Bear,第一個直接基類。
c. Endangered,第二個直接基類,它本身沒有基類。
d. Panda,初始化 Panda 本身的成員,然後運行它的建構函式的函數體。
總是按建構函式啟動並執行逆序調用解構函式。在我們的例子中,調用解構函式的次序是 ~Panda, ~Endangered, ~Bear, ~ZooAnimal。
4:在多重繼承情況下,遇到二義性轉換的可能性更大。例如,如果有 print 函數的重載版本:
void print(const Bear&);void print(const Endangered&);Panda ying_yang("ying_yang");print(ying_yang); // error: ambiguous
導致一個編譯時間錯誤,指出該調用是二義性的。
5:假定所有根基類都將它們的解構函式適當定義為虛函數,那麼,無論通過哪種指標類型刪除對象,虛解構函式的處理都是一致的:
// each pointer points to a Pandadelete pz; // pz is a ZooAnimal*delete pb; // pb is a Bear*delete pp; // pp is a Panda*delete pe; // pe is a Endangered*
假定這些指標每個都向 Panda 對象,則每種情況下發生完全相同的解構函式調用次序。解構函式調用的次序是建構函式次序的逆序:通過虛機制調用 Panda 解構函式。隨著 Panda 解構函式的執行,依次調用 Endangered、Bear 和ZooAnimal 的解構函式。
像單繼承的情況一樣,如果具有多個基類的類定義了自己的解構函式,該解構函式只負責清除衍生類別。如果衍生類別定義了自己的複製建構函式或賦值操作符,則類負責複製(賦值)所有的基類子部分。只有衍生類別使用複製建構函式或賦值操作符的合成版本,才自動複製或賦值基類部分。
6:在多重繼承下,名字尋找同時檢察所有的基類繼承子樹——在我們的例子中,並行尋找Endangered 子樹和 Bear/ZooAnimal 子樹。如果在多個子樹中找到該名字,則那個名字的使用必須顯式指定使用哪個基類;否則,該名字的使用是二義性的。
假定 Bear 類和 Endangered 類都定義了名為 print 的成員,如果 Panda 類沒有定義該成員,則ying_yang.print(cout);這樣的語句將導致編譯時間錯誤。
派生只是導致潛在的二義性,如果沒有 Panda 對象調用 print,就可以避免這個二義性。如果每個 print 調用明確指出想要哪個版本——Bear::print 還是Endangered::print,也可以避免錯誤。只有在存在使用該成員的二義性嘗試的時候,才會出錯。
即使兩個繼承的函數有不同的形參表,也會產生錯誤。類似地,即使函數在一個類中是私人的而在另一個類中是公用或受保護的,也是錯誤的。
可以通過指定使用哪個類解決二義性:ying_yang.Endangered::print(cout);
7:在多重繼承下,一個基類可以在派生層次中出現多次。比如IO 庫類:
多重繼承的類從它的每個父類繼承狀態和動作,如果 IO 類型使用常規繼承,則每個 iostream 對象可能包含兩個 ios 子物件:一個包含在它的 istream 子物件中,另一個包含在它的 ostream 子物件中,從設計角度講,這個實現正是錯誤的。
使用虛繼承解決這類問題。虛繼承是一種機制,類通過虛繼承指出它希望共用其虛基類的狀態。在虛繼承下,對給定虛基類,無論該類在派生層次中作為虛基類出現多少次,只繼承一個共用的基類子物件。
istream 和 ostream 類對它們的基類進行虛繼承。通過使基類成為虛基類,istream 和 ostream 指定,如果其他類(如 iostream )同時繼承它們兩個,則衍生類別中只出現它們的公用基類的一個副本。通過在衍生的資料行表中包含關鍵字virtual 設定虛基類:
class istream : public virtual ios { ... };class ostream : virtual public ios { ... };class iostream: public istream, public ostream { ... };
8:考慮下面的例子:
class Panda : public Bear, public Raccoon, public Endangered {};
必須在提出虛派生的任意實際需要之前進行虛派生(在例中,Bear類和 Raccoon 類的虛派生)。只有在使用 Panda 的聲明時,虛繼承才是必要的。
通過用關鍵字 virtual 修改聲明,將基類指定為通過虛繼承派生。例如,下面的聲明使 ZooAnimal 類成為 Bear 類和 Raccoon 類的虛基類:
// the order of the keywords public and virtual is not significantclass Raccoon : public virtual ZooAnimal { /* ... */ };class Bear : virtual public ZooAnimal { /* ... */ };
9:假定通過多個派生路徑繼承名為 X 的成員,有下面三種可能性:
a. 如果在每個路徑中 X 表示同一虛基類成員,則沒有二義性,因為共用該成員的單個執行個體。
b. 如果在某個路徑中 X 是虛基類的成員,而在另一路徑中 X 是後代衍生類別的成員,也沒有二義性——特定衍生類別執行個體的優先順序高於共用虛基類執行個體。
c. 如果沿每個繼承路徑 X 表示後代衍生類別的不同成員,則該成員的直接存取是二義性的。
10:每個類只初始化自己的直接基類。在應用於虛基類的情況,這個初始化策略會失敗。如果使用常規規則,就可能會多次初始化虛基類。類將沿著包含該虛基類的每個繼承路徑初始化。在 ZooAnimal 樣本中,使用常規規則將導致 Bear類和 Raccoon 類都試圖初始化 Panda 對象的 ZooAnimal 類部分。
為瞭解決這個重複初始化問題,從具有虛基類的類繼承的類對初始化進行特殊處理。在虛派生中,由最低層衍生類別的建構函式初始化虛基類。在我們的例子中,當建立 Panda 對象的時候,只有 Panda 建構函式控制怎樣初始化 ZooAnimal基類。
雖然由最低層衍生類別初始化虛基類,但是任何直接或間接繼承虛基類的類一般也必須為該基類提供自己的初始化式。只要可以建立虛基類衍生類別類型的獨立對象,該類就必須初始化自己的虛基類,這些初始化式只有建立中間類型的對象時使用。
在我們的層次中,可以有 Bear、Raccoon 或 Panda 類型的對象。建立 Panda 對象的時候,它是最低層衍生類別型並控制共用的 ZooAnimal 基類的初始化:建立Bear 對象(或 Raccoon 對象)的時候,不涉及更低層的衍生類別型。在這種情況下,Bear(或 Raccoon)建構函式像平常一樣直接初始化它們的 ZooAnimal 基類:
Bear::Bear(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Bear") { }Raccoon::Raccoon(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Raccoon") { }
雖然 ZooAnimal 不是 Panda 的直接基類,但是 Panda 建構函式也初始化ZooAnimal 基類:
Panda::Panda(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Panda"), Bear(name, onExhibit), Raccoon(name, onExhibit), Endangered(Endangered::critical), sleeping_flag(false) { }Bear winnie("pooh"); // Bear constructor initializes ZooAnimalRaccoon meeko("meeko"); // Raccoon constructor initializes ZooAnimalPanda yolo("yolo"); // Panda constructor initializes ZooAnimal
當建立 Panda 對象的時候,
首先使用建構函式初始化列表中指定的初始化式構造 ZooAnimal 部分;接下來,構造 Bear 部分。忽略 Bear 的用於 ZooAnimal 建構函式初始化列表的初始化式;然後,構造 Raccoon 部分,再次忽略 ZooAnimal 初始化式;最後,構造 Panda 部分。
11:無論虛基類出現在繼承層次中任何地方,總是在構造非虛基類之前構造虛基類。
下面TeddyBear的派生中,有兩個虛基類:ToyAnimal基類和派生 Bear 的間接基類 ZooAnimal:
class Character { /* ... */ };class BookCharacter : public Character { /* ... */ };class ToyAnimal { /* ... */ };class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal{ /* ... */ };
TeddyBear 的虛基類的構造次序是先 ZooAnimal 再 ToyAnimal。一旦構造了虛基類,就按聲明次序調用非虛基類的建構函式:首先是 BookCharacter,它導致調用 Character 建構函式,然後是 Bear。因此,為了建立 TeddyBear 對象,按下面次序調用建構函式:
ZooAnimal(); // Bear‘s virtual base classToyAnimal(); // immediate virtual base classCharacter(); // BookCharacter‘s nonvirtual base classBookCharacter(); // immediate nonvirtual base classBear(); // immediate nonvirtual base classTeddyBear(); // most derived class
在這裡,由最低層衍生類別 TeddyBear 指定用於 ZooAnimal 和 ToyAnimal 的初始化式。
在合成複製建構函式中使用同樣的構造次序,在合成賦值操作符中也是按這個次序給基類賦值。保證調用基類解構函式的次序與建構函式的調用次序相反。
C++多重繼承