歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

菱形繼承問題(鑽石問題)

我看到網上有很多人都在說虛繼承和虛表的關系,我一直很郁悶,虛繼承和虛表沒有什麼太大的關系,虛表是當有虛函數出現的時候才會有的,光是使用虛繼承是不會有虛表的!當然也就不會有虛表指針!!從我後面的截圖可以看到使用虛繼承對象的內存構造中並沒有出現虛表指針!跟虛繼承有關的是一個虛基類表(vbptr),這個表在調試的監視窗口是看不見的,但有了虛繼承之後使用sizeof可以明顯看出,類型所占內存的大小擴大了4個字節!

況且問題就不一樣好麼?虛繼承解決的是二義性的問題,而虛函數是為了解決多態的問題。

 

仔細看過之後就會發現虛繼承表和虛函數表不是一個東西,地址不一樣存放的東西也不一樣,上圖中虛函數表地址是0x008c7b40虛繼承表地址是0x008c7b48,虛函數表存放虛函數指針,虛繼承表存放的是整型偏移量

 

在學習C++的時候,菱形繼承問題絕對是一個不可避免的重點問題,那麼什麼是菱形繼承問題呢?下圖就是,長得像不像鑽石?我畫圖確實很難看

因為C++允許多繼承,當繼承關系像上圖這樣子的時候,就會出現這樣子的情況

A類是基類,B裡面有個A我表示為B(A),C裡面有個A我表示為C(A)

那麼D裡面有B和C我表示為D(B(A)C(A))

當我們想去使用D裡面的A的時候,或者說訪問A的部分值,在說白了究竟哪個A才是屬於D的,D中的A究竟是B的A還是C的A?

1 class A 
2 {};
3 class B :public A
4 {};
5 class C :public A
6 {};
7 class D :public B, public C
8 {};

這麼寫可就錯了,有的編譯器甚至都不讓你通過,直接給你報錯

這很令人尴尬不是麼,就算編譯期讓你通過了,也不要試圖這樣去通過D的對象訪問其內部的A對象,這會讓編譯器很糾結

但是很簡單給個vitual就好了

1 class A 
2 {};
3 class B :virtual public A
4 {};
5 class C :virtual public A
6 {};
7 class D : public B, public C
8 {};

他有了一個屬於自己的A,調用A中的變量或者函數的時候就會去屬於自己的A中調用,就不會讓編譯期糾結了

那麼當我們想要通過D類對象訪問D類對象內部的B中的A類部分呢?

因為繼承的關系C的A和B的A應該是一樣的(因為都在D類對象中的關系),訪問到D中B的A部分之後,轉而去調用d本身的A

讓我們給每個類都加上一個成員變量,從內存的角度看這個問題

類結構改成A:_a   B:_b   C:_c   D:_d,表示各個類獨有的成員變量,虛繼承關系不變

就會得到這樣的結果

你們有看到虛表指針一類的東西麼?沒有虛表指針_vptr!

也就是說,內存布局是這樣子的

我們查詢一下d變量的內存地址,打開內存窗口看一下

上圖中最上面的兩個紅色區域就是d變量中B和C所占用的內存空間,每一行都是四個字節,因為我們已經知道沒有被初始化的int變量的內存值用16進制表示就是cc cc cc cc所以在B和C中各自的第一行就是我們需要分別訪問的A了,看著他們倆的A都不太對勁,不太像是一個對象的樣子,我們把這兩串神秘的值相減,結果為8

可以這麼理解,01116bdc和01116be4之間的差值為8,就是說從B索引A的地址偏移比從C索引A的地址偏移要多上8位(在對象d中來說),從上圖這個內存布局來看,對象d中B的位置和C的位置正好相差8個字節,就是說B中的A和C中的A存放的已經是一個地址了,這個地址會索引到真正使用的“A”就是上圖中的綠色區域.

然而這並不是直接索引到綠色區域穿的偏移地址,其實一看你就會發現,這個地址過於大了,其實這個地址確實是偏移地址沒錯,但是指向的並不是直接的A的地址快,而是一個vbptr虛繼承表,這個表和虛表可不是一個東西!!!!其中存放的是真正的偏移量(不是偏移地址了),這個偏移量是指從當前對象的地址開始算,往後偏移的字節數。

vbptr虛基類指針會出現在類的一開始的內存地址(雖然你看到的是從int型開始的),然後這個內存地址加上我剛才所說的存儲好的偏移量,就能索引了,而且有了這個表和指針之後,很大程度上減少了內存空間的占用

Copyright © Linux教程網 All Rights Reserved