QUESTION:什么是鑽石繼承?
ANSWER:假設我們已經有了兩個類Father1和Father2,他們都是類GrandFather的子類。現在又有一個新類Son,這個新類通過多繼承機制對類Father1和Father2都進行了繼承,此時類GrandFather、Father1、Father2和Son的繼承關系是一個菱形,仿佛一個鑽石,因此這種繼承關系在C++中通常被稱為鑽石繼承(或菱形繼承)。
示意圖:
示例:
1 #include<iostream> 2 using namespace std; 3 class GrandFather{ //第一層基類GrandFather 4 public: 5 GrandFather()=default; 6 GrandFather(int v):value(v){} 7 int value; 8 }; 9 10 class Father1:public GrandFather{ //第二層基類Father1 11 public: 12 Father1()=default; 13 Father1(int v):GrandFather(v){} 14 void set_value(int value){ //設置value的值 15 this->value=value; 16 } 17 }; 18 19 class Father2:public GrandFather{ //第二層基類Father2 20 public: 21 Father2()=default; 22 Father2(int v):GrandFather(v){} 23 int get_value(){ //獲取value的值 24 return this->value; 25 } 26 }; 27 28 class Son:public Father1,public Father2{ //第三次層類Son 29 public: 30 Son()=default; 31 Son(int v):Father1(v),Father2(v){} 32 }; 33 34 int main(){ 35 Son s(10); 36 s.set_value(20); 37 cout<<s.get_value()<<endl; 38 return 0; 39 }
QUESTION:上例中明明將對象s的value值設置成了20,為什么最終value的輸出卻還是初始化值10?
ANSWER:解決這個問題我們首先需要知道對象s是如何構造的,還是上面的示例:
1 #include<iostream> 2 using namespace std; 3 class GrandFather{ //第一層基類GrandFather 4 public: 5 GrandFather()=default; 6 GrandFather(int v):value(v){ 7 cout<<"調用了GrandFather類的構造函數"<<endl; 8 } 9 int value; 10 }; 11 12 class Father1:public GrandFather{ //第二層基類Father1 13 public: 14 Father1()=default; 15 Father1(int v):GrandFather(v){ 16 cout<<"調用Father1類的構造函數"<<endl; 17 } 18 void set_value(int v){ //設置value的值 19 this->value=v; 20 } 21 }; 22 23 class Father2:public GrandFather{ //第二層基類Father2 24 public: 25 Father2()=default; 26 Father2(int v):GrandFather(v){ 27 cout<<"調用Father2類的構造函數"<<endl; 28 } 29 int get_value(){ //獲取value的值 30 return this->value; 31 } 32 }; 33 34 class Son:public Father1,public Father2{ //第三次子類Son 35 public: 36 Son()=default; 37 Son(int v):Father1(v),Father2(v){ 38 cout<<"調用Son類的構造函數"<<endl; 39 } 40 }; 41 42 int main(){ 43 Son s(10); 44 s.set_value(20); 45 cout<<s.get_value()<<endl; 46 return 0; 47 }
我們發現在創建類Son的對象s時,第一層基類GrandFather的構造函數被調用了兩次,這說明系統在創建對象s前會先創建兩個獨立的基類子對象(分別是Father1的對象和Father2的對象),然后再創建包含這兩個子對象的對象s,如圖:
由此可見,對象s中包含兩個分屬於不同子對象的成員變量value。而方法set_value()和方法get_value()雖然都是對象s的成員函數,但由於其也分屬於對象s中的不同子對象,故其操作所針對的成員變量value不是同一個value,而是方法所在的子對象所包含的value,即上例中方法set_value()的功能是重新設置Father1類所創建的子對象的value值,而方法get_value()是返回Father2類所創建的子對象的value值。
1 int main(){ 2 Son s(10); 3 s.set_value(20); 4 cout<<"Father1類創建的子對象的value值:"<<s.Father1::value<<endl; 5 cout<<"Father2類創建的子對象的value值:"<<s.Father2::value<<endl; 6 return 0; 7 }
QUESTION:如何解決鑽石繼承中存在的“數據不一致”問題?
ANSWER:在C++中通常利用虛基類和虛繼承來解決鑽石繼承中的“數據不一致”問題
特別注意:
1.什么是虛繼承和虛基類
• 虛繼承:在繼承定義中包含了virtual關鍵字的繼承關系
• 虛基類:在虛繼承體系中通過關鍵字virtual繼承而來的基類
2.為什么使用虛基類和虛繼承
• 使用虛基類和虛繼承可以讓一個指定的基類在繼承體系中將其成員數據實例共享給從該基類直接或間接派生出的其它類,即使從不同路徑繼承來的同名數據成員在內存中只有一個拷貝,同一個函數名也只有一個映射
1 #include<iostream> 2 using namespace std; 3 class GrandFather{ //第一層基類GrandFather 4 public: 5 GrandFather()=default; 6 GrandFather(int v):value(v){ 7 cout<<"調用了GrandFather類的構造函數"<<endl; 8 } 9 int value; 10 }; 11 12 class Father1:virtual public GrandFather{ //第二層基類Father1,虛繼承基類GrandFather 13 public: 14 Father1()=default; 15 Father1(int v):GrandFather(v){ 16 cout<<"調用Father1類的構造函數"<<endl; 17 } 18 void set_value(int value){ //設置value的值 19 this->value=value; 20 } 21 }; 22 23 class Father2:virtual public GrandFather{ //第二層基類Father2,虛繼承基類GrandFather 24 public: 25 Father2()=default; 26 Father2(int v):GrandFather(v){ 27 cout<<"調用Father2類的構造函數"<<endl; 28 } 29 int get_value(){ //獲取value的值 30 return this->value; 31 } 32 }; 33 34 class Son:public Father1,public Father2{ //第三次子類Son 35 public: 36 Son()=default; 37 Son(int v):Father1(v),Father2(v),GrandFather(v) { 38 cout<<"調用Son類的構造函數"<<endl; 39 } 40 }; 41 42 int main(){ 43 Son s(10); 44 s.set_value(20); 45 cout<<s.get_value()<<endl; 46 return 0; 47 }
上例中的鑽石繼承中,由於基類Father1和基類Father2采用虛繼承的方式來繼承類GrandFather,此時對象s中類Father1和類Father2創建的子對象共享GrandFather類創建的子對象,如圖:
此時對象s中成員變量value只有一個,且被Father1類創建的子對象和Father2類創建的子對象所共享,即方法set_value()和方法get_value()操作的value是同一個成員變量。
3.構造函數的調用順序
• 首先按照虛基類的聲明順序調用虛基類的構造函數
• 然后按照非虛基類的聲明順序調用非虛基類的構造函數
• 之后調用派生類中成員對象的構造函數
• 最后調用派生類自己的構造函數
示例:
1 #include<iostream> 2 using namespace std; 3 class One{ 4 public: 5 int one; 6 One(int o):one(o){ 7 cout<<"調用類One的構造函數"<<endl; 8 } 9 }; 10 11 class Two{ 12 public: 13 int two; 14 Two(int t):two(t){ 15 cout<<"調用類Two的構造函數"<<endl; 16 } 17 }; 18 19 class Three{ 20 public: 21 int three; 22 Three(int t):three(t){ 23 cout<<"調用類Three的構造函數"<<endl; 24 } 25 }; 26 27 class Four{ 28 public: 29 Four(){ 30 cout<<"調用類Four的構造函數"<<endl; 31 } 32 }; 33 34 class Five{ 35 public: 36 int five; 37 Five(int f):five(f){ 38 cout<<"調用類Five的構造函數"<<endl; 39 } 40 }; 41 42 class Six:public One,virtual Two,virtual Three,public Five{ 43 public: 44 Six(int value):One(value),Two(value),Three(value) ,Five(value){ //在派生類的構造函數的成員初始化列表中必須列出對虛基類構造函數的調用 45 cout<<"調用類Six的構造函數"<<endl; 46 } 47 private: 48 Four four; 49 }; 50 51 int main(){ 52 Six six(10); 53 return 0; 54 }
4.使用虛基類和虛繼承時的一些注意事項:
• 在派生類對象中,同名的虛基類只產生一個虛基類子對象,而同名的非虛基類則各產生一個非虛基類子對象
• 虛基類的子對象是由最后派生出來的類的構造函數通過調用虛基類的構造函數來初始化的。因此在派生類的構造函數的成員初始化列表中必須列出對虛基類構造函數的調用,如果沒有列出,則表示使用該虛基類的默認構造函數。
• 虛基類並不是在聲明基類時聲明的,而是在聲明派生類時通過指定其繼承該基類的方式來聲明的。