多重繼承地內(nèi)存分布_第1頁
多重繼承地內(nèi)存分布_第2頁
多重繼承地內(nèi)存分布_第3頁
已閱讀5頁,還剩15頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、指針的比較再以上面Bottom 類繼承關(guān)系為例討論,下面這段代碼會打印Equal嗎?1 Bottom* b = new Bottom();2 Right* r = b;33 if (r = b)4 printf( "Equal! /n ");先明確下這兩個指針實際上是指向不同地址的,r指針實際上在b指針所指地址上偏移 8字節(jié),但是,這些C+內(nèi)部細節(jié)不能告訴 C+程序員,所以C+編譯器在比較r和b時, 會把r減去8字節(jié),然后再來比較,所以打印出的值是"Equal".多重繼承首先我們先來考慮一個很簡單(non-virtual)的多重繼承。看看下面這個C+類層

2、次結(jié)構(gòu)。1 class Top2 3 public :4 int a;5 ;66 class Left : public Top7 8 public :9 int b;10 ;1213 class Right : public Top14 15 public :16 int c;17 ;18Right18 class Bottom : public Left, public19 20 public :21 int d;22 ;24用UML表述如下:Leftb : intBottom注意到Top類實際上被繼承了兩次,(這種機制在Eiffel中被稱作repeated inheritanee),這就

3、意味著在一個bottom 對象中實際上有兩個a屬性 (attributes ,可以通過 bottom.Left:a 和 bottom.Right:a 訪問)。那么Left、Right、Bottom 在內(nèi)存中如何分布的呢?我們先來看看簡單的Left和Right內(nèi)存分布:Right類的布局和Left是一樣的,因此我這里就沒再畫圖了。刺猬注意到上面類各自的第一個屬性都是繼承自Top類,這就意味著下面兩個賦值語句1 Left* left = new Left();2 Top* top = left;left和top實際上是指向兩個相同的地址,我們可以把Left對象當作一個 Top對象(同樣也可以把 R

4、ight對象當Top對象來使用)。但是Botom 對象呢?GCC是這樣處理的:但是現(xiàn)在如果我們upcast 一個Bottom指針將會有什么結(jié)果?1 Bottom* bottom = new Bottom。;2 Left* left = bottom;這段代碼運行正確。這是因為 GCC選擇的這種內(nèi)存布局使得我們可以把Bottom 對象當作Left對象,它們兩者(Left部分)正好相同。但是,如果我們把Bottom 對象指針upcast 到Right對象呢?1 Right* right = bottom;如果我們要使這段代碼正常工作的話,我們需要調(diào)整指針指向Bottom 中相應(yīng)的部分。通過調(diào)整,我

5、們可以用right指針訪問Bottom 對象,這時Bottom 對象表現(xiàn)得就如 Right 對象。但是bottom 和right指針指向了不同的內(nèi)存地址。最后,我們考慮下:1 Top* top = bottom;恩,什么結(jié)果也沒有,這條語句實際上是有歧義(ambiguous)的,編譯器會報錯:error:'Top' is an ambiguous base of 'Bottom'。其實這兩種帶有歧義的可能性可以用如下語句加以區(qū)分:1 Top* topL = (Left*) bottom;2 Top* topR = (Right*) bottom;這兩個賦值語句執(zhí)

6、行之后,topL和left指針將指向同一個地址,同樣topR和right也將指向同一個地址。虛擬繼承為了避免上述Top類的多次繼承,我們必須虛擬繼承類Top。1 class Top2 3 public :4 int a;5 ;66 class Left : virtual public Top7 8 public :9 int b;10 ;1213 class Right : virtual public Top14 15 public :16 int c;17 ;1818 class Bottom : public Left, public Right19 20 public :21 int

7、 d;22 ;24上述代碼將產(chǎn)生如下的類層次圖(其實這可能正好是你最開始想要的繼承方式)。對于程序員來說,這種類層次圖顯得更加簡單和清晰,不過對于一個編譯器來說,這就復(fù)雜得多了。我們再用Bottom 的內(nèi)存布局作為例子考慮,它可能是這樣的:BottomLeft:'Top: aLeft: hRight :c這種內(nèi)存布局的優(yōu)勢在于它的開頭部分(Left部分)和Left的布局正好相同,我們可以很輕易地通過一個Left指針訪問一個 Bottom 對象。不過,我們再來考慮考慮Right:1 Right* right = bottom;虛擬繼承為了避免上述Top類的多次繼承,我們必須虛擬繼承類To

8、p。1 class Top2 3 public :4 int a;5 ;66 class Left : virtual public Top7 8 public :9 int b;10 ;1213 class Right : virtual public Top14 15 public :16 int c;17 ;1818 class Bottom : public Left, public Right19 20 public :21 int d;22 ;24上述代碼將產(chǎn)生如下的類層次圖(其實這可能正好是你最開始想要的繼承方式)。對于程序員來說,這種類層次圖顯得更加簡單和清晰,不過對于一個編譯

9、器來說,這就復(fù)雜得多了。我們再用Bottom 的內(nèi)存布局作為例子考慮,它可能是這樣的:這種內(nèi)存布局的優(yōu)勢在于它的開頭部分(Left部分)和Left的布局正好相同,我們可以很輕易地通過一個Left指針訪問一個 Bottom 對象。不過,我們再來考慮考慮Right:1 Right* right = bottom;這里我們應(yīng)該把什么地址賦值給right指針呢?理論上說,通過這個賦值語句,我們可以把這個right指針當作真正指向一個 Right對象的指針(現(xiàn)在指向的是Bottom)來使用。但實 際上這是不現(xiàn)實的! 一個真正的Right對象內(nèi)存布局和 Bottom 對象Right部分是完全不同的,所以其

10、實我們不可能再把這個 upcasted的bottom 對象當作一個真正的 right對象 來使用了。而且,我們這種布局的設(shè)計不可能還有改進的余地了。這里我們先看看實際上內(nèi)存是怎么分布的,然后再解釋下為什么這么設(shè)計。上圖有兩點值得大家注意。第一點就是類中成員分布順序是完全不一樣的(實際上可以說是正好相反)。第二點,類中增加了vptr指針,這些是被編譯器在編譯過程中插入到類中的(在設(shè)計類時如果使用了虛繼承,虛函數(shù)都會產(chǎn)生相關(guān)vptr)。同時,在類的構(gòu)造函數(shù)中會對相關(guān)指針做初始化,這些也是編譯器完成的工作。Vptr指針指向了一個"virtual table ”。在類中每個虛基類都會存在與之

11、對應(yīng)的一個vptr指針。為了給大家展示virtual table 作用,考慮下如下代碼。1 Bottom* bottom = new Bottom。;2 Left* left = bottom;3 int p = left->a;第二條的賦值語句讓 left指針指向和bottom同樣的起始地址(即它指向Bottom 對象的“頂部”)。我們來考慮下第三條的賦值語句。1 movl left , %eax # % eax = left2 movl (% eax), % eax# % eax = left .vptr.Left3 movl (% eax), % eax# % eax = virt

12、ual base offset4 addl left , % eax # % eax = left + virtual base offset5 movl (% eax), % eax # % eax = left .a6 movl % eax , p# p = left .a總結(jié)下,我們用left指針去索引(找到)virtual table,然后在virtual table中獲取到虛基類的偏移(virtual base offset, vbase),然后在left指針上加上這個偏移量,這樣我們就獲取到了 Bottom 類中Top類的開始地址。從上圖中,我們可以看到對于Left指針,它的vir

13、tual base offset 是20 ,如果我們假設(shè) Bottom 中每個成員都是 4字節(jié)大小,那么 Left指針加上20字節(jié)正好是成員a的地址。我們同樣可以用相同的方式訪問Bottom 中 Right 部分。1 Bottom* bottom = new Bottom。;2 Right* right = bottom;3 int p = right->a;right指針就會指向在 Bottom 對象中相應(yīng)的位置。right T這里對于p的賦值語句最終會被編譯成和上述left相同的方式訪問a。唯一的不同是就是vptr,我們訪問的vptr現(xiàn)在指向了 virtual table另一個地址,

14、我們得到的virtual base offset 也變?yōu)?2。我們畫圖總結(jié)下:20當然,關(guān)鍵點在于我們希望能夠讓訪問一個真正單獨的Right對象也如同訪問一個經(jīng)過upcasted (到Right對象)的 Bottom 對象一樣。這里我們也在Right對象中引入 vptrs 。Mabie for Left virtual offset : int of依眈 to top ; Int = 0Rightpoint; to rviable Tar Rightvptr Right : vtaLle for Right Riciht::i:: iniTop.a : intMrtualoffset: Int

15、 = Snffseittop : im = 0 typelnfo : lypdnfo for Right1JOK,現(xiàn)在這樣的設(shè)計終于讓我們可以通過一個Right指針訪問Bottom對象了。不過,需要提醒的是以上設(shè)計需要承擔一個相當大的代價:我們需要引入虛函數(shù)表,對象底層也必須擴展以支持一個或多個虛函數(shù)指針,原來一個簡單的成員訪問現(xiàn)在需要通過虛函數(shù)表兩次間接尋址(編譯器優(yōu)化可以在一定程度上減輕性能損失)。Down cast ing如我們猜想,將一個指針從一個派生類到一個基類的轉(zhuǎn)換(casting)會涉及到在指針上添加偏移量??赡苡信笥巡孪?,dow ncast ing 個指針僅僅減去一些偏移量就行

16、了吧。實際上, 非虛繼承情況下確實是這樣,但是,對于虛繼承來說,又不得不引入其它的復(fù)雜問題。這里我們在上面的例子中添加一些繼承關(guān)系:1 class Ano therBottom : public Left, public Right2 3 public:4 inte;5 intf;6 ;這個繼承關(guān)系如下圖所示那么現(xiàn)在考慮如下代碼1 Bottom* bottoml = new Bottom。;2 AnotherBottom* bottom2 = new AnotherBottom();3 Top* topi = bottoml;4 Top* top2 = bottom2;5 Left* left

17、 = static_cast <Left*>(top1);F面這圖展示了 Bottom 和AnotherBottom的內(nèi)存布局,同時也展示了各自top指針所指向的位置。topiAno therBottomvpthLeftLeft; :bvptrRjghtRight: cAnotherBQvtorTi;ftop2 >Top:;a現(xiàn)在我們來考慮考慮從top1到left的static_cast ,注意這里我們并不清楚對于top1指針指向的對象是 Bottom 還是AnotherBottom。這里是根本不能編譯通過的!因為根本不能確認topi運行時需要調(diào)整的偏移量(對于Bottom

18、是20 ,對于AnotherBottom是24)。所以編譯器將會提出錯誤:error: cannot convert from base 'Top' to derived type 'Left'via virtual base 'Top'。這里我們需要知道運行時信息,所以我們需要使用dyn amic_cast :1 Left* left = dyn amic_cast <Left*>(top1);不過,編譯器仍然會報錯的error: cannot dyn amic_cast 'top' (of type 'cl

19、ass Top*') totype 'class Left*' (source type is not polymorphic)。 關(guān)鍵問題在于使用dynamic_cast(和使用typeid 一樣)需要知道指針所指對象的運行時信息。但是,回頭看看上面的結(jié)構(gòu)圖,我們就會發(fā)現(xiàn)topi指針所指的僅僅是一個整數(shù)成員a。編譯器沒有在 Bottom 類中包含針對top的vptr,它認為這完全沒有必要。為了強制編譯器在 Bottom 中包含top的vptr,我們可以在top類里面添加一個虛析構(gòu)函數(shù)。1 class Top2 3 public :4 virtual Top() 5 i

20、nt a;6 ;這就迫使編譯器為 Top類添加了一個vptr。下面來看看 Bottom 新的內(nèi)存布局:Bottompoint; Tovtab le forBonamwptr.Ljeft : vtible f or Bottomwlnualoffset: int =20: intoffset to top : mt = 0wprtr.Right : vtable for Bottom-_ point 5 v.itypeirft-;評“info kiEsouomFlight:堆:Intvirtual base offset: int = 12tiattom:d : inioffsei to top

21、 : ini = 6wptrTap : vt&ble for Bottomtype info : typeinfo for BoltcimTop:.a . inxvirtual bue off ret: Irit - 0offset to t op : int = 20typeirfo :iypeinfo for BonomTmpTopO是的,其它派生類(Left、Right)都會添加一個 vptr.top ,編譯器為dynamic_cast生成了 一個庫函數(shù)調(diào)用。1 left = _dynamic_cast(top1, typeinfo_for_Top, typeinfo_for_L

22、eft, -1);_dynamic_cast 定義在 libstdc+( 對應(yīng)的頭文件是 cxxabi.h),有了 Top、Left 和 Bottom 的類型信息,轉(zhuǎn)換得以執(zhí)行。其中,參數(shù) -1代表的是類Left和類Top之間的關(guān)系未明。女口 果想詳細了解,請參看 tin fo.cc的實現(xiàn)。二級指針這里的問題初看摸不著頭腦,但是細細想來有些問題還是顯而易見的。這里我們考慮一個 問題,還是以上節(jié)的Dow ncast ing中的類繼承結(jié)構(gòu)圖作為例子。1 Bottom* b = new Bottom。;2 Right* r = b;(在把b指針的值賦值給指針 r時,b指針將加上8字節(jié),這樣r指針才指

23、向Bottom 對象 中Right部分)。因此我們可以把 Bottom*類型的值賦值給 Right*對象。但是Bottom* 和 Right*兩種類型的指針之間賦值呢?1 Bottom* bb = &b;2 Right* rr = bb;編譯器能通過這兩條語句嗎?實際上編譯器會報錯 :error: in valid con version from'Bottom*' to 'Right*'為什么?不妨反過來想想,如果能夠?qū)b賦值給rr,如下圖所示。所以這里bb和rr兩 個指針都指向了 b , b和r都指向了 Bottom對象的相應(yīng)部分。那么現(xiàn)在考慮考慮

24、如果給 *rr 賦值將會發(fā)生什么。1 *rr = b;注意*rr是Right*類型(一級)的指針,所以這個賦值是有效的!這個就和我們上面給r指針賦值一樣(*rr是一級的Right*類型指針,而r同樣是一級Right*指針)。所以,編譯器將采用相同的方式實現(xiàn)對*rr的賦值操作。實際上,我們又要調(diào)整b的值,加上8字節(jié),然后賦值給*rr,但是現(xiàn)在*rr其實是指向b的!如下圖呃,如果我們通過rr訪問Bottom對象,那么按照上圖結(jié)構(gòu)我們能夠完成對Bottom對象的訪問,但是如果是用b來訪問Bottom 對象呢,所有的對象引用實際上都偏移了8字節(jié)一一明顯是錯誤的!總而言之,盡管*a和*b之間能依靠類繼承

25、關(guān)系相互轉(zhuǎn)化,而*a和*b不能有這種推論。虛基類的構(gòu)造函數(shù)編譯器必須要保證所有的虛函數(shù)指針要被正確的初始化。特別是要保證類中所有虛基類的構(gòu)造函數(shù)都要被調(diào)用,而且還只能調(diào)用一次。如果你寫代碼時自己不顯示調(diào)用構(gòu)造函數(shù),編譯器會自動插入一段構(gòu)造函數(shù)調(diào)用代碼。這將會導(dǎo)致一些奇怪的結(jié)果,同樣考慮下上面的類繼承結(jié)構(gòu)圖,不過要加入構(gòu)造函數(shù)。1 class Top2 3 public :4 Top() a = -1; 5 Top( int _a) a = _a; 6 int a;7 ;88 class Left : public Top9 10 public :11 Left() b = -2;12 Left( int _a, int _b) : Top(_a) b = _b; 13 int b;14 ;1615 class Right : public Top16 17 public :18 Right() c = -3;19 Right( int _a, int _c) : Top(_a) c = _c; 20 int c;21 ;2422 class Bottom : public Left, public Right23 24 public :25 Bottom() d = -4 ; 26 Bottom( int _a, int _b, int

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論