C++继承详解三 ----菱形继承、虚继承
轉(zhuǎn)載:http://blog.csdn.net/pg_dog/article/details/70175488
今天呢,我們來(lái)講講菱形繼承與虛繼承。這兩者的講解是分不開的,要想深入了解菱形繼承,你是繞不開虛繼承這一點(diǎn)的。它倆有著什么關(guān)系呢?值得我們來(lái)剖析。?
菱形繼承也叫鉆石繼承,它是多繼承的一種特殊實(shí)例吧,它的基本架構(gòu)如下圖:?
在我們的設(shè)想中,D所對(duì)應(yīng)的對(duì)象模型應(yīng)該如下圖所示:
?
下面我們來(lái)用一段代碼驗(yàn)證一下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
上面顯示的大小似乎證實(shí)了我們的猜想,但實(shí)際上對(duì)象模型不是這樣的,如下圖所示?
?
但是你會(huì)發(fā)現(xiàn),這里面存在一個(gè)問(wèn)題,對(duì)象D中有兩個(gè)‘a(chǎn)’,存在數(shù)據(jù)冗余的問(wèn)題,如果對(duì)象B,C中有兩個(gè)同名的函數(shù)或同名成員變量(本例中的變量‘a(chǎn)’),那么對(duì)象D在調(diào)用該函數(shù)或該成員變量時(shí),該選擇調(diào)用哪個(gè)呢?這也就可以看出還存有二義性問(wèn)題。那么該如何處理呢??
解決二義性問(wèn)題很簡(jiǎn)單,你在調(diào)用函數(shù)時(shí)加上作用域運(yùn)算符(::),但是數(shù)據(jù)冗余問(wèn)題還是沒有解決。那么編譯器是如何處理這兩個(gè)問(wèn)題的呢??
為了解決二義性問(wèn)題和數(shù)據(jù)冗余問(wèn)題,C++引入了虛繼承這一概念。下面重點(diǎn)來(lái)看虛繼承。
虛繼承?
虛繼承又稱共享繼承,是面向?qū)ο缶幊痰囊环N技術(shù),是指一個(gè)指定的基類,在繼承體系結(jié)構(gòu)中,將其成員數(shù)據(jù)實(shí)例共享給也從這個(gè)基類直接或間接派生的其他類。虛擬繼承是多重繼承中特有的概念,虛擬繼承就是為了解決多重繼承而出現(xiàn)的。?
這里我想引入《C++ Primer》這本書中對(duì)虛繼承的有關(guān)描述。
在C++語(yǔ)言中我們通過(guò)虛繼承的機(jī)制來(lái)解決共享問(wèn)題。虛繼承的目的是令某個(gè)類作出聲明,承諾共享它的基類。其中,共享的基類子對(duì)象稱其為虛基類。在這種機(jī)制下,不論虛基類在繼承體系中出現(xiàn)了多少次,在派生類中都只含有唯一一個(gè)共享的虛基類子對(duì)象。
這里還有一個(gè)概念,虛基類。虛基類是通過(guò)virtual繼承而來(lái)的派生類的基類。例如:B虛繼承了A,所以A是B的虛基類,而不是說(shuō)A是虛基類。?
看下圖了解普通基類與虛基類的區(qū)別:
按照上面的說(shuō)法,在對(duì)象D中應(yīng)該只含有一個(gè)共享的虛基類子對(duì)象,也就是例子中的_a。確實(shí),這樣就解決了數(shù)據(jù)冗余與二義性問(wèn)題。我們來(lái)驗(yàn)證上面的的說(shuō)法。(為了計(jì)算簡(jiǎn)單,我將上例中每個(gè)類成員變量變?yōu)檎蝘nt)
下面我們來(lái)看一段代碼:
class A { public:A(){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}void print(){printf("A");}int _a; };class B :virtual public A //B虛繼承A { public:B(){cout << "B()" << endl;}~B(){cout << "~B()" << endl;}int _b; };class C :virtual public A //C虛繼承A { public:C(){cout << "C()" << endl;}~C(){cout << "~C()" << endl;}int _c; }; class D :public B, public C { public:D(){cout << "D()" << endl;}~D(){cout << "~D()" << endl;}int _d;};int main() {cout << sizeof(A)<< endl;cout << sizeof(B)<< endl;cout << sizeof(C)<< endl;cout << sizeof(D)<< endl;B bb;C cc;D dd;dd.B::_a = 1;dd.C::_a = 2;dd._b = 3;dd._c = 4;dd._d = 5;system("pause");return 0; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
B和C都是虛擬繼承,?
按照我們之前的推理,對(duì)象D的結(jié)構(gòu)應(yīng)該如圖所示:?
?
我們來(lái)通過(guò)vs2013調(diào)試中的內(nèi)存窗口來(lái)驗(yàn)證一下:?
?
看到這個(gè)結(jié)果是不是嚇壞寶寶了?和我們預(yù)測(cè)的完全不一樣,對(duì)象A和B中的_a跑到了最底部,這種結(jié)構(gòu)明顯沒有了數(shù)據(jù)冗余和二義性問(wèn)題了,這是怎么實(shí)現(xiàn)的呢?這就要引入一新的概念——虛基類表。?
虛基類表:又稱虛基表,編譯器會(huì)給虛繼承而來(lái)的派生類生成一個(gè)指針vbptr指向一個(gè)虛基表,而虛基表中存放的是偏移量。?
我們來(lái)看對(duì)象D中的對(duì)象B,它的第一部分(第一行)就是虛基類表指針vbptr,它存的是虛基表的地址,虛基表中存的是共享基類成員變量_a的相對(duì)此位置的偏移量,我們來(lái)看看,“01259b60”是個(gè)地址,利用內(nèi)存窗口我們可以發(fā)現(xiàn)里面存著兩部分第一行“00 00 00 00”和第二行“00 00 00 14”,虛基表中分兩部分:第一部分存儲(chǔ)的是對(duì)象相對(duì)于存放vptr指針的偏移量(在這就是“00 00 00 00”,偏移量為0),第二部分存儲(chǔ)的是對(duì)象中基類對(duì)象部分相對(duì)于存放vbptr指針的地址的偏移量(在這就是“00 00 00 14”),?
即20(十六進(jìn)制下14就是十進(jìn)制的20),也就是說(shuō)偏移量是20個(gè)字節(jié),你可以用他們的地址相減驗(yàn)證一番。你可以看圖數(shù)一下,而對(duì)象D中的C的第一部分也是一樣,是個(gè)虛基表,存的一樣也是偏移量,它存的地址“00369b68”,里面是“00 00 00 0c”即十進(jìn)制的12,即偏移量為12字節(jié)。可以看下圖:
下面我再講一個(gè)概念——虛函數(shù),這會(huì)在下篇文章多態(tài)中重點(diǎn)講解,但是這里有必要了解一下。?
虛函數(shù)——類的成員函數(shù)前面加上virtual關(guān)鍵字,則這個(gè)函數(shù)被稱為虛函數(shù)。
虛函數(shù):用于定義類型特定行為的成員函數(shù)。通過(guò)引用和指針對(duì)虛函數(shù)的調(diào)用直到運(yùn)行時(shí)才被解析,依據(jù)是引用或指針?biāo)壎▽?duì)象的類型。(《C++ Primer》中定義)
虛函數(shù)重寫(覆蓋):當(dāng)在子類定義了一個(gè)和父類完全相同的虛函數(shù)時(shí),則稱這個(gè)這個(gè)子類的函數(shù)重寫了(覆蓋了)父類的虛函數(shù)。?
既然說(shuō)到這,就有必要區(qū)分一下幾個(gè)概念:?
重載:在同一作用域內(nèi),函數(shù)名相同,參數(shù)不同,返回值可不同的一對(duì)函數(shù)被稱為重載。?
隱藏(重定義):在不同作用域(一般指基類和派生類),函數(shù)名相同,參數(shù)列表也相同,但不需要virtual關(guān)鍵字的一組函數(shù)稱為隱藏。?
覆蓋:不在同一作用域(一般指派生類和基類),完全相同(協(xié)變除外)基類中函數(shù)必須有virtual關(guān)鍵字的一對(duì)函數(shù)被稱為重定義。
注:?
1,基類中定義了虛函數(shù),在派生類中該函數(shù)始終保持虛函數(shù)的特性。?
2,只有類的成員函數(shù)才能定義為虛函數(shù)。?
3,靜態(tài)成員函數(shù)不能定義為虛函數(shù)。?
4,如果在類外定義虛函數(shù),只能在聲明處加virtual關(guān)鍵字,類外定義函數(shù)時(shí)不能加virtual關(guān)鍵字。?
5,構(gòu)造函數(shù)不能為虛函數(shù)。?
6,最好不要將賦值運(yùn)算符重載定義為虛函數(shù),因?yàn)槭褂萌菀谆煜?
7,不要在構(gòu)造函數(shù)和析構(gòu)函數(shù)調(diào)用虛函數(shù),在構(gòu)造函數(shù)和析構(gòu)函數(shù)中對(duì)象是不完整的,可能會(huì)發(fā)生未定義的行為。?
8,最好將基類的析構(gòu)函數(shù)定義為虛函數(shù)。(注:雖然基類的析構(gòu)函數(shù)和派生類的析構(gòu)函數(shù)名稱不一樣,但構(gòu)成覆蓋,因?yàn)榫幾g器做了特殊處理)?
9,虛繼承只對(duì)虛繼承子類后面派生出的子類有影響,對(duì)虛繼承自雷本身沒有影響。
純虛函數(shù)?
純虛函數(shù)——在成員函數(shù)的后面加上=0,則成員函數(shù)為純虛函數(shù)。一個(gè)純虛函數(shù)無(wú)需定義,但也可以定義,但是必須在類外,也就是說(shuō)我們不能在類內(nèi)部為一個(gè)帶有=0的函數(shù)提供函數(shù)體。包含純虛函數(shù)的類被稱為抽象類,也叫接口類。抽象類不能實(shí)例化出對(duì)象。他只是作為基類服務(wù)于派生類,如果派生類不對(duì)基類的虛函數(shù)進(jìn)行覆蓋,那他仍將是抽象基類。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
繼承和友元?
友元關(guān)系不能繼承,也就是說(shuō)基類友元不能訪問(wèn)子類私有和保護(hù)成員。
繼承和靜態(tài)成員?
基類中定義了靜態(tài)成員,則整個(gè)繼承體系中只有一個(gè)這樣的成員。無(wú)論派生出多少的子類,都只有一個(gè)靜態(tài)成員實(shí)例。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)
總結(jié)
以上是生活随笔為你收集整理的C++继承详解三 ----菱形继承、虚继承的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使命召唤手游兰博基尼怎么获得
- 下一篇: 试管婴儿怎样做