C++中虛擬繼承的概念
爲了解決從不同途徑繼承來的同名的數據成員在內存中有不同的拷貝造成數據不一致問題,將共同基類設置爲虛基類。這時從不同的路徑繼承過來的同名數據成員在內存中就只有一個拷貝,同一個函數名也只有一個映射。這樣不僅就解決了二義性問題,也節省了內存,避免了數據不一致的問題。
class 派生類名:virtual 繼承方式 基類名
virtual是關鍵字,聲明該基類爲派生類的虛基類。
在多繼承情況下,虛基類關鍵字的作用範圍和繼承方式關鍵字相同,只對緊跟其後的基類起作用。
聲明瞭虛基類之後,虛基類在進一步派生過程中始終和派生類一起,維護同一個基類子對象的拷貝。
C++虛擬繼承
◇概念:
C++使用虛擬繼承(Virtual Inheritance),解決從不同途徑繼承來的同名的數據成員在內存中有不同的拷貝造成數據不一致問題,將共同基類設置爲虛基類。這時從不同的路徑繼承過來的同名數據成員在內存中就只有一個拷貝,同一個函數名也只有一個映射。
◇解決問題:
解決了二義性問題,也節省了內存,避免了數據不一致的問題。
◇同義詞:
虛基類(把一個動詞當成一個名詞而已)
當在多條繼承路徑上有一個公共的基類,在這些路徑中的某幾條匯合處,這個公共的基類就會產生多個實例(或多個副本),若只想保存這個基類的一個實例,可以將這個公共基類說明爲虛基類。
◇語法:
class 派生類: virtual 基類1,virtual 基類2,...,virtual 基類n
{
...//派生類成員聲明
};
◇執行順序
首先執行虛基類的構造函數,多個虛基類的構造函數按照被繼承的順序構造;
執行基類的構造函數,多個基類的構造函數按照被繼承的順序構造;
執行成員對象的構造函數,多個成員對象的構造函數按照申明的順序構造;
執行派生類自己的構造函數;
析構以與構造相反的順序執行;
mark
從虛基類直接或間接派生的派生類中的構造函數的成員初始化列表中都要列出對虛基類構造函數的調用。但只有用於建立對象的最派生類的構造函數調用虛基類的構造函數,而該派生類的所有基類中列出的對虛基類的構造函數的調用在執行中被忽略,從而保證對虛基類子對象只初始化一次。
在一個成員初始化列表中同時出現對虛基類和非虛基類構造函數的調用時,虛基類的構造函數先於非虛基類的構造函數執行。
◇因果:
多重繼承->二義性->虛擬繼承解決
◇二義性:
1: //-----------------------------------------------------
2: //名稱:blog_virtual_inherit.cpp
3: //說明:C++虛擬繼承學習演示
4: //環境:VS2005
5: //blog:pppboy.blog.163.com
6: //----------------------------------------------------
7: #include "stdafx.h"
8: #include <iostream>
9: using namespace std;
10:
11: //Base
12: class Base
13: {
14: public:
15: Base(){cout << "Base called..."<< endl;}
16: void print(){cout << "Base print..." <<endl;}
17: private:
18: };
19:
20: //Sub
21: class Sub //定義一個類 Sub
22: {
23: public:
24: Sub(){cout << "Sub called..." << endl;}
25: void print(){cout << "Sub print..." << endl;}
26: private:
27: };
28:
29: //Child
30: class Child : public Base , public Sub //定義一個類Child 分別繼承自 Base ,Sub
31: {
32: public:
33: Child(){cout << "Child called..." << endl;}
34: private:
35: };
36:
37: int main(int argc, char* argv[])
38: {
39: Child c;
40:
41: //不能這樣使用,會產生二意性,VC下error C2385
42: //c.print();
43:
44: //只能這樣使用
45: c.Base::print();
46: c.Sub::print();
47:
48: system("pause");
49: return 0;
50: }
◇多重繼承:
1: //-----------------------------------------------------
2: //名稱:blog_virtual_inherit.cpp
3: //說明:C++虛擬繼承學習演示
4: //環境:VS2005
5: //blog:pppboy.blog.163.com
6: //----------------------------------------------------
7: #include "stdafx.h"
8: #include <iostream>
9: using namespace std;
10:
11: int gFlag = 0;
12:
13: class Base
14: {
15: public:
16: Base(){cout << "Base called : " << gFlag++ << endl;}
17: void print(){cout << "Base print" <<endl;}
18: };
19:
20: class Mid1 : public Base
21: {
22: public:
23: Mid1(){cout << "Mid1 called" << endl;}
24: private:
25: };
26:
27: class Mid2 : public Base
28: {
29: public:
30: Mid2(){cout << "Mid2 called" << endl;}
31: };
32:
33: class Child:public Mid1, public Mid2
34: {
35: public:
36: Child(){cout << "Child called" << endl;}
37: };
38:
39: int main(int argc, char* argv[])
40: {
41: Child d;
42:
43: //不能這樣使用,會產生二意性
//d.print();
45:
46: //只能這樣使用
47: d.Mid1::print();
48: d.Mid2::print();
49:
50: system("pause");
51: return 0;
52: }
53:
//output
Base called : 0
Mid1 called
Base called : 1
Mid2 called
Child called
Base print
Base print
◇虛擬繼承
在派生類繼承基類時,加上一個virtual關鍵詞則爲虛擬繼承
1: //-----------------------------------------------------
2: //名稱:blog_virtual_inherit.cpp
3: //說明:C++虛擬繼承學習演示
4: //環境:VS2005
5: //blog:pppboy.blog.163.com
6: //----------------------------------------------------
7: #include "stdafx.h"
8: #include <iostream>
9: using namespace std;
10:
11: int gFlag = 0;
12:
13: class Base
14: {
15: public:
16: Base(){cout << "Base called : " << gFlag++ << endl;}
17: void print(){cout << "Base print" <<endl;}
18: };
19:
20: class Mid1 : virtual public Base
21: {
22: public:
23: Mid1(){cout << "Mid1 called" << endl;}
24: private:
25: };
26:
27: class Mid2 : virtual public Base
28: {
29: public:
30: Mid2(){cout << "Mid2 called" << endl;}
31: };
32:
33: class Child:public Mid1, public Mid2
34: {
35: public:
36: Child(){cout << "Child called" << endl;}
37: };
38:
39: int main(int argc, char* argv[])
40: {
41: Child d;
42:
43: //這裏可以這樣使用
44: d.print();
45:
46: //也可以這樣使用
47: d.Mid1::print();
48: d.Mid2::print();
49:
50: system("pause");
51: return 0;
52: }
53:
//output
1: Base called : 0
2: Mid1 called
3: Mid2 called
4: Child called
5: Base print
6: Base print
7: Base print
8: 請按任意鍵繼續. . .
◇通過輸出的比較
1.在多繼承情況下,虛基類關鍵字的作用範圍和繼承方式關鍵字相同,只對緊跟其後的基類起作用。
2.聲明瞭虛基類之後,虛基類在進一步派生過程中始終和派生類一起,維護同一個基類子對象的拷貝。
3.觀察類構造函數的構造順序,拷貝也只有一份。
◇與虛函數關係
虛擬繼承與虛函數有一定相似的地方,但他們之間是絕對沒有任何聯繫的。
再想一次:虛擬繼承,虛基類,虛函數。