多重繼承的先后問題上節最后的例子是為下面討論一個被稱之為“菱形問題”作鋪墊的,在Java中沒有多重繼承,也許沒有這種現象,C++中很容易出現。由一個基類派生出兩個類出來,以后新定義一個類,并從這兩個類多重繼承,這樣就出現菱形問題了。也就是說,基類的公有或保護成員,必然被兩個派生類同時繼承,這兩個類同時派生一個新類時,同名成員就產生了沖突。 下面我們先從簡單問題入手,先不要看結果圖,考慮一下結果應該是什么。小雅當初認為三行輸出的ID都應為“WD8503025”,但事實不是這樣。
#include <iostream> #include <string> using namespace std; class CBase { public: string id; }; class CDerive1 : public CBase { public: void show1() { cout << "CDerive1: " << id << endl; } }; class CDerive2 : public CBase { public: void show2() { cout << "CDerive2: " << id << endl; } }; class CSon : public CDerive2, public CDerive1 { }; int main ( ) { CSon s; s.CDerive1::id = "WD8503026"; s.CDerive2::id = "WD8503027"; s.CBase::id = "WD8503025"; s.show1(); s.show2(); cout << "BASE: " << s.CBase::id << endl; return 0; }
運行結果: CDerive1: WD8503026 CDerive2: WD8503025 BASE: WD8503025 通過設置斷點不難看出,當前實例是CBase的“孫子”,而“父親”有2個,每個“父親”都將“爺爺”復制了一份。如果不指定哪個“父親”的“父親”,默認將第一個繼承的“父親”的“父親”當作“爺爺”。上例中先繼承CDerive2類,所以31行和34行的“s.CBase::id”等價于“s.CDerive2::CBase::id”。
下面的例子是先定義一個“孫子”的實例,并將地址輸出。再將這個實例的地址分別賦給CDerive1和CDerive2類型的指針變量,并輸出指針地址。再將這2個地址分別賦給CBase的2個指針變量,并輸出其地址。大家仍然不看結果,考慮一下答案應該是什么?
#include <iostream> #include <string> using namespace std; class CBase { string id; public: void show() { cout << id << endl; } }; class CDerive1 : public CBase { }; class CDerive2 : public CBase { }; class CSon : public CDerive2, public CDerive1 { }; int main ( ) { CSon s; cout << &s << endl; cout << "---------" << endl; CDerive1 *pd1 = &s; cout << pd1 << endl; CDerive2 *pd2 = &s; cout << pd2 << endl; cout << "---------" << endl; CBase *pb1 = pd1; cout << pb1 << endl; CBase *pb2 = pd2; cout << pb2 << endl; //CBase *pb = &s; //編譯有錯 return 0; }
運行結果: 0035FB20 --------- 0035FB3C 0035FB20 --------- 0035FB3C 0035FB20 從上例可以看出,由于2個“父親”因而復制出2個“爺爺”,2個“爺爺”的地址也不同。
解決以上問題只要用C++的“虛繼承”就可以了。“虛繼承”就是在實例中,基類不管繼承多少個,只復制一份。
#include <iostream> #include <string> using namespace std; class CBase { public: string id; }; //因為虛繼承,CBase類在此不產生副本 class CDerive1 : virtual public CBase { public: void show1() { cout << "CDerive1: " << id << endl; } }; //因為虛繼承,CBase類在此不產生副本 class CDerive2 : virtual public CBase { public: void show2() { cout << "CDerive2: " << id << endl; } }; class CSon : public CDerive2, public CDerive1 { }; int main ( ) { CSon s; s.CDerive1::id = "WD8503026"; s.CDerive2::id = "WD8503027"; s.CBase::id = "WD8503025"; s.show1(); s.show2(); cout << "BASE: " << s.CBase::id << endl; return 0; }
運行結果: CDerive1: WD8503025 CDerive2: WD8503025 BASE: WD8503025 因為2個“父親”都不產生“爺爺”的副本,所以“孫子”這兒就只有一個“爺爺”的副本。