溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vtbl(虛函數(shù)表)與vptr(虛函數(shù)表指針)

發(fā)布時(shí)間:2020-07-19 13:47:22 來(lái)源:網(wǎng)絡(luò) 閱讀:290 作者:li19910110 欄目:編程語(yǔ)言

類的虛函數(shù)表是一塊連續(xù)的內(nèi)存,每個(gè)內(nèi)存單元中記錄一個(gè)JMP指令的地址
  注意的是,編譯器會(huì)為每個(gè)有虛函數(shù)的類創(chuàng)建一個(gè)虛函數(shù)表,該虛函數(shù)表將被該類的所有對(duì)象共享。類的每個(gè)虛成員占據(jù)虛函數(shù)表中的一行。如果類中有N個(gè)虛函數(shù),那么其虛函數(shù)表將有N*4字節(jié)的大小。   虛函數(shù)(Virtual Function)是通過(guò)一張?zhí)摵瘮?shù)表(Virtual Table)來(lái)實(shí)現(xiàn)的。簡(jiǎn)稱為V-Table。在這個(gè)表中,主要是一個(gè)類的虛函數(shù)的地址表,這張表解決了繼承、覆蓋的問(wèn)題,保證其真實(shí)反應(yīng)實(shí)際的函數(shù)。這樣,在有虛函數(shù)的類的實(shí)例中這個(gè)表被分配在了這個(gè)實(shí)例的內(nèi)存中,所以,當(dāng)用父類的指針來(lái)操作一個(gè)子類的時(shí)候,這張?zhí)摵瘮?shù)表就顯得由為重要了,它就像一個(gè)地圖一樣,指明了實(shí)際所應(yīng)該調(diào)用的函數(shù)。   編譯器應(yīng)該是保證虛函數(shù)表的指針存在于對(duì)象實(shí)例中最前面的位置(這是為了保證取到虛函數(shù)表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。 這意味著可以通過(guò)對(duì)象實(shí)例的地址得到這張?zhí)摵瘮?shù)表,然后就可以遍歷其中函數(shù)指針,并調(diào)用相應(yīng)的函數(shù)。
 
在方法定義時(shí)加上virtual,表示此方法是虛擬方法,可供子類覆蓋,修改父類的執(zhí)行 

構(gòu)造函數(shù)不能用虛擬,因?yàn)橛靡矝](méi)用,不管是在棧上構(gòu)造對(duì)象,還是在堆上構(gòu)造對(duì)象,也不管你以后是否使用父類的指針或引用來(lái)指向或引用這個(gè)對(duì)象,在構(gòu)造的那“一瞬間”,總歸要指明要構(gòu)造對(duì)象的具體類型,所以,對(duì)象在構(gòu)造過(guò)程中不存在運(yùn)行時(shí)動(dòng)態(tài)綁定的多態(tài)行為。 

例子,假如A是B的父類, 
A* p = new B(); 
則對(duì)于虛擬函數(shù)f,可以通過(guò)A類的指針p直接調(diào)用到B類的函數(shù),這就是運(yùn)行時(shí)的多態(tài): 
p->f(); 
B類的對(duì)象卻必須通過(guò)“A* p = new B();”來(lái)構(gòu)造,顯然不能通過(guò)“A* p = new A();”來(lái)構(gòu)造一個(gè)B類對(duì)象——這是荒唐的,這只能構(gòu)造一個(gè)A類的對(duì)象。所以構(gòu)造函數(shù)虛擬無(wú)意義。 
但析構(gòu)函數(shù)就不同了,p明明是個(gè)A類的指針,如果析構(gòu)函數(shù)不是虛擬的,那么,你后面就必須這樣才能安全的刪除這個(gè)指針: 
delete (B*)p; 

但如果構(gòu)造函數(shù)是虛擬的,就可以在運(yùn)行時(shí)動(dòng)態(tài)綁定到B類的析構(gòu)函數(shù),直接: 
delete p; 
就可以了。這就是虛析構(gòu)函數(shù)的作用。而事實(shí)上,在運(yùn)行時(shí),你并不是總是能知道p所指對(duì)象的實(shí)際類型從而進(jìn)行強(qiáng)制轉(zhuǎn)換,所以,C++語(yǔ)言既然要支持多態(tài),也就必須支持虛擬析構(gòu)。 

編譯器總是根據(jù)類型來(lái)調(diào)用類成員函數(shù)。但是一個(gè)派生類的指針可以安全地轉(zhuǎn)化為一個(gè)基類的指針。這樣刪除一個(gè)基類的指針的時(shí)候,C++不管這個(gè)指針指向一個(gè)基類對(duì)象還是一個(gè)派生類的對(duì)象,調(diào)用的都是基類的析構(gòu)函數(shù)而不是派生類的。如果你依賴于派生類的析構(gòu)函數(shù)的代碼來(lái)釋放資源,而沒(méi)有重載析構(gòu)函數(shù),那么會(huì)有資源泄漏。 

所以建議的方式是將析構(gòu)函數(shù)聲明為虛函數(shù)。如果你使用MFC,并且以CObject或其派生類為基類,那么MFC已經(jīng)為你做了這件事情;CObject的析構(gòu)函數(shù)是虛函數(shù)。一個(gè)函數(shù)一旦聲明為虛函數(shù),那么不管你是否加上virtual 修飾符,它在所有派生類中都成為虛函數(shù)。但是由于理解明確起見(jiàn),建議的方式還是加上virtual 修飾符。 

C++不把虛析構(gòu)函數(shù)直接作為默認(rèn)值的原因是虛函數(shù)表的開(kāi)銷以及和C語(yǔ)言的類型的兼容性。有虛函數(shù)的對(duì)象總是在開(kāi)始的位置包含一個(gè)隱含的虛函數(shù)表指針成員。如果是對(duì)于MFC類CPoint和CSize這樣的小型類,增加一個(gè)指針就增加了很多內(nèi)存占用,而且使得其內(nèi)存表示和基類POINT和SIZE不一致。如果兩個(gè)類的內(nèi)存表示一致,那么這樣你可以安全地把一個(gè)類的指針或數(shù)組當(dāng)作另一個(gè)類的指針或數(shù)組使用。 

通過(guò)基類的指針去刪除派生類的對(duì)象,而基類又沒(méi)有虛析構(gòu)函數(shù)時(shí),結(jié)果將是不可確定的。這意味著編譯器生成的代碼將會(huì)做任何它喜歡的事:重新格式化你的硬盤(pán),給你的老板發(fā)電子郵件,把你的程序源代碼傳真給你的對(duì)手,無(wú)論什么事都可能發(fā)生。(實(shí)際運(yùn)行時(shí)經(jīng)常發(fā)生的是,派生類的析構(gòu)函數(shù)永遠(yuǎn)不會(huì)被調(diào)用。 

實(shí)現(xiàn)虛函數(shù)需要對(duì)象附帶一些額外信息,以使對(duì)象在運(yùn)行時(shí)可以確定該調(diào)用哪個(gè)虛函數(shù)。對(duì)大多數(shù)編譯器來(lái)說(shuō),這個(gè)額外信息的具體形式是一個(gè)稱為vptr(虛函數(shù)表指針)的指針。vptr指向的是一個(gè)稱為vtbl(虛函數(shù)表)的函數(shù)指針數(shù)組。每個(gè)有虛函數(shù)的類都附帶有一個(gè)vtbl。當(dāng)對(duì)一個(gè)對(duì)象的某個(gè)虛函數(shù)進(jìn)行請(qǐng)求調(diào)用時(shí),實(shí)際被調(diào)用的函數(shù)是根據(jù)指向vtbl的vptr在vtbl里找到相應(yīng)的函數(shù)指針來(lái)確定的。 

虛函數(shù)實(shí)現(xiàn)的細(xì)節(jié)不重要,但基類中最好成績(jī)要有.此時(shí)就有基本的一條是,無(wú)故的聲明虛析構(gòu)函數(shù)和永遠(yuǎn)不去聲明一樣是錯(cuò)誤的。實(shí)際上,很多人這樣總結(jié):當(dāng)且僅當(dāng)類里包含至少一個(gè)虛函數(shù)的時(shí)候才去聲明虛析構(gòu)函數(shù)。


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI