虚基类的作用
1 概念
????首先還是先給出虛繼承和虛基類的定義。
虛繼承:在繼承定義中包含了virtual關(guān)鍵字的繼承關(guān)系;虛基類:在虛繼承體系中的通過virtual繼承而來的基類,需要注意的是:
CSubClass : public virtual CBase {}; 其中CBase稱之為CSubClass的虛基類,而不是說CBase就是個虛基類,因為CBase還可以作為不是虛繼承體系中的基類。
? ? Virtual 在C++中就是采用了這個詞意,不可以在語言模型中直接調(diào)用或體現(xiàn)的,但是確實是存在可以被間接的方式進行調(diào)用或體現(xiàn)的。比如:虛函數(shù)必須要通過一種間接的運行時(而不是編譯時)機制才能夠激活(調(diào)用)的函數(shù),而虛繼承也是必須在運行時才能夠進行定位訪問的一種體制。存在,但間接。其中關(guān)鍵就在于存在、間接和共享這三種特征。
????對于虛函數(shù)而言,這三個特征是很好理解的,間接性表明了他必須在運行時根據(jù)實際的對象來完成函數(shù)尋址,共享性表象在基類會共享被子類重載后的虛函數(shù),其實指向相同的函數(shù)入口。
????對于虛繼承而言,這三個特征如何理解呢?存在即表示虛繼承體系和虛基類確實存在,間接性表明了在訪問虛基類的成員時同樣也必須通過某種間接機制來完成(下面模型中會講到),共享性表象在虛基類會在虛繼承體系中被共享,而不會出現(xiàn)多份拷貝。
????那現(xiàn)在可以解釋語法小節(jié)中留下來的那個問題了,“ 為什么一旦出現(xiàn)了虛基類,就必須在每一個繼承類中都必須包含虛基類的初始化語句”。由上面的分析可以知道,虛基類是被共享的,也就是在繼承體系中無論被繼承多少次,對象內(nèi)存模型中均只會出現(xiàn)一個虛基類的子對象(這和多繼承是完全不同的),這樣一來既然是共享的那么每一個子類都不會獨占,但是總還是必須要有一個類來完成基類的初始化過程(因為所有的對象都必須被初始化,哪怕是默認的),同時還不能夠重復進行初始化, 那到底誰應該負責完成初始化呢?C++標準中(也是很自然的) 選擇在每一次繼承子類中都必須書寫初始化語句(因為每一次繼承子類可能都會用來定義對象),而在最下層繼承子類中實際執(zhí)行初始化過程。所以上面在每一個繼承類中都要書寫初始化語句,但是在創(chuàng)建對象時,而僅僅會在創(chuàng)建對象用的類構(gòu)造函數(shù)中實際的執(zhí)行初始化語句,其他的初始化語句都會被壓制不調(diào)用。2 模型
????為了實現(xiàn)上面所說的三種語義含義,在考慮對象的實現(xiàn)模型(也就是內(nèi)存模型)時就很自然了。在C++中對象實際上就是一個連續(xù)的地址空間的語義代表,我們來分析虛繼承下的內(nèi)存模型。
(1) 存在
????也就是說在對象內(nèi)存中必須要包含虛基類的完整子對象,以便能夠完成通過地址完成對象的標識。那么至于虛基類的子對象會存放在對象的那個位置(頭、中間、尾部)則由各個編譯器選擇,沒有差別。(在VC8中無論虛基類被聲明在什么位置,虛基類的子對象都會被放置在對象內(nèi)存的尾部)。(2) 間接
????間接性表明了在 直接虛繼承子類中一定包含了某種指針(偏移或表格)來完成通過子類訪問虛基類子對象(或成員)的間接手段(因為虛基類子對象是共享的,沒有確定關(guān)系),至于采用何種手段由編譯器選擇。(在VC8中在子類中放置了一個虛基類指針vbc,該指針指向虛函數(shù)表中的一個slot,該slot中存放則虛基類子對象的偏移量的負值,實際上就是個以補碼表示的int類型的值,在計算虛基類子對象首地址時,需要將該偏移量取絕對值相加,這個主要是為了和虛表中只能存放虛函數(shù)地址這一要求相區(qū)別,因為地址是原碼表示的無符號int類型的值)。(3) 共享
????共享表明了在對象的內(nèi)存空間中僅僅能夠包含一份虛基類的子對象,并且通過某種間接的機制來完成共享的引用關(guān)系。在介紹完整個內(nèi)容后會附上測試代碼,體現(xiàn)這些內(nèi)容。3 性能
????由于有了間接性和共享性兩個特征,所以決定了虛繼承體系下的對象在訪問時必然會在時間和空間上與一般情況有較大不同。(1) 時間
????在通過繼承類對象訪問虛基類對象中的成員(包括數(shù)據(jù)成員和函數(shù)成員)時,都必須通過某種間接引用來完成,這樣會增加引用尋址時間(就和虛函數(shù)一樣),其實就是調(diào)整this指針以指向虛基類對象,只不過這個調(diào)整是運行時間接完成的。(在VC8中通過打開匯編輸出,可以查看*.cod文件中的內(nèi)容,在訪問虛基類對象成員時會形成三條mov間接尋址語句,而在訪問一般繼承類對象時僅僅只有一條mov常量直接尋址語句)(2) 空間
由于共享所以不存在對象內(nèi)存中保存多份虛基類子對象的拷貝,這樣較之多繼承節(jié)省空間。4?實例1,無虛函數(shù)的虛基類
?當一個基類被聲明為虛基類后,即使它成為了多繼承鏈路上的公共基類,最后的派生類中也只有它的一個備份。例如:
則在類CDerive12的對象中,僅有類CBase的一個對象數(shù)據(jù)
虛基類的特點:
? ? ? ?虛基類構(gòu)造函數(shù)的參數(shù)必須由最新派生出來的類負責初始化(即使不是直接繼承);
? ? ? ?虛基類的構(gòu)造函數(shù)先于非虛基類的構(gòu)造函數(shù)執(zhí)行。
(1) 子派生類對象的值:
??
???? 從上例可以看出,在類CDerived12的構(gòu)造函數(shù)初始化表中,調(diào)用了間接基類CBase的構(gòu)造函數(shù),這對于非虛基類是非法的,但對于虛基類則是合法且必要的。
對于派生類CDerived1和CDerived2,不論是其內(nèi)部實現(xiàn),還是實例化的對象,基類CBase是否是它們的虛基類是沒有影響的。受到影響的是它們的派生類CDerived12,因為它從兩條路徑都能到達CBase。
(2) 內(nèi)存布局
備注:
vbtable : 虛基類指針表
vftable : 虛函數(shù)指針表
?????????
??? 由此可知,其公共基類的構(gòu)造函數(shù)只調(diào)用了一次,并且優(yōu)先于非基類的構(gòu)造函數(shù)調(diào)用;并且發(fā)現(xiàn),子派生類的對象obj的成員變量的值只有一個,所以,當公共基類CBase被聲明為虛基類后,雖然它成為CDerive1和CDerive2的公共基類,但子派生類CDerive12中也只有它的一個備份??梢宰屑毐容^與例2的運行結(jié)果有什么不同。
5 實例2,有虛函數(shù)的虛基類
下述實例析構(gòu)函數(shù)為虛函數(shù)
(1) 內(nèi)存布局
6 實例3,非虛繼承
(1) 內(nèi)存布局
CDerive1 直接從CBase搬移過來,再加上自己的
CDerive2 直接從CDerive1搬移過來,再加上自己的
本文轉(zhuǎn)自:
http://blog.csdn.net/caomiao2006/article/details/4463664
總結(jié)
- 上一篇: 世界上最快的浏览器(mxnitro浏览器
- 下一篇: 赴美生子诚实签的五大谣言!!