C++:鑽石繼承與虛繼承

QUESTION:什麼是鑽石繼承?ios

ANSWER:假設咱們已經有了兩個類Father1和Father2,他們都是類GrandFather的子類。如今又有一個新類Son,這個新類經過多繼承機制對類Father1和Father2都進行了繼承,此時類GrandFather、Father一、Father2和Son的繼承關係是一個菱形,彷彿一個鑽石,所以這種繼承關係在C++中一般被稱爲鑽石繼承(或菱形繼承)。函數

示意圖:this

示例:spa

 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?code

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,如圖:blog

因而可知,對象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:如何解決鑽石繼承中存在的「數據不一致」問題?three

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.使用虛基類和虛繼承時的一些注意事項:

在派生類對象中,同名的虛基類只產生一個虛基類子對象,而同名的非虛基類則各產生一個非虛基類子對象

• 虛基類的子對象是由最後派生出來的類的構造函數經過調用虛基類的構造函數來初始化的。所以在派生類的構造函數的成員初始化列表中必須列出對虛基類構造函數的調用,若是沒有列出,則表示使用該虛基類的默認構造函數。

虛基類並非在聲明基類時聲明的,而是在聲明派生類時經過指定其繼承該基類的方式來聲明的。

相關文章
相關標籤/搜索