最近在看C++的東西,看了好多多重繼承的問題,有些說的很簡單,有的說的很繁瑣,把資料整理一下。在多重繼承中,基類的構造函數的調用次序既不受派生類構造函數初始化列表中出現的基類構造函數的影響,也不受基類在構造函數初始化列表中的出現次序的影響,它按照基類在類派生列表中的出現次序依次調用相應的基類構造函數。析構順序與構造順序逆序進行。
多重繼承中,派生類的指針或引用可以轉換為其任意基類的指針或引用。因此,這種轉換更可能遇到二義性問題。
在多重繼承中,成員函數中使用的名字的查找首先在函數本身進行,如果不能在本地找到名字,就繼續在成員的類中查找,然後同時(並行)查找所有基類繼承子樹。多重繼承的派生類有可能從兩個或多個基類繼承同名成員,對該成員不加限定的使用是二義性的。
注意,多重繼承中首先發生名字查找。你可能會感到吃驚的是,即使兩個繼承的同名函數有不同的形參表,也會產生錯誤。類似地,即使函數在一個類中是私有的而在另一個類中是公有或者受保護的,也是錯誤的。或者,在一個類給定義了函數,而在另一個類中沒有定義,調用仍是錯誤的。例如:
[cpp] #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)
#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設置虛基類,例如:
[cpp]
class istream : public virtual ios {...};
class ostream : virtual public ios {...};
class iostream : public istream, public ostream {...};
class istream : public virtual ios {...};
class ostream : virtual public ios {...};
class iostream : public istream, public ostream {...};
假定通過多個派生路徑繼承名為X的成員,有下面三種可能性:
1)如果在每個路徑中X表示同一虛基類成員,則沒有二義性,因為共享該成員的單個實例;
2)如果在某個路徑中X是虛基類的成員,而在另一路徑中X是後代派生類的成員,也沒有二義性——特定派生類實例的優先級高於共享虛基類實例。
3)如果沿每個繼承路徑X表示後代派生類的不同成員,則該成員的直接訪問是二義性的。
例如:
[cpp]
#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;
}
#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 virtualToyAnimal { };
直觀繼承圖為:
按聲明次序檢查直接基類,確定是否存在虛基類。上例中,首先檢查BookCharacter的繼承子樹,然後檢查Bear的繼承子樹,最後檢查 ToyAnimal的繼承子樹。按從根類開始向下到最低層派生類的次序檢查每個子樹。在這裡依次檢查到ZooAnimal和ToyAnimal為虛基類。
TeddyBear的虛基類的構造次序是先ZooAnimal再ToyAnimal(檢查到的順序)。一旦構造了虛基類,就按聲明次序調用非虛基類的構造函數:首先是BookCharacter,它導致調用Character構造函數,然後是Bear。在這裡,由最低層派生類TeddyBear指定用於 ZooAnimal和ToyAnimal的初始化式。
當然,對於析構函數的調用順序與構造函數相反。
示例代碼如下:
[cpp]
#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 ...
#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 ...