详解虚函数的实现过程之单继承(2)
從匯編分析一下下面的多態(tài)模擬結(jié)構(gòu)
利用 父類(lèi)指針指向子類(lèi)的特性,可以間接調(diào)用各子類(lèi)中的虛函數(shù)。
雖然指針類(lèi)型為父類(lèi),但由于虛表的排列順序是按虛函數(shù)在類(lèi)繼承層次中首次聲明的順序依次排列的,因此,只要繼承了父類(lèi),其派生類(lèi)的虛表中的父類(lèi)部分的排列就與父類(lèi)的一致,子類(lèi)新定義的虛函數(shù)將會(huì)按照聲明順序緊跟其后。
我們給Speak函數(shù)傳遞任何一個(gè)基于CPerson的派生對(duì)象地址都能正確調(diào)用虛函數(shù)ShowSpeak,那么當(dāng)我們調(diào)用虛函數(shù)時(shí),它是如何實(shí)現(xiàn)調(diào)用的呢?請(qǐng)看下面分析
分析如下:
地址401108的eax是this指針,即對(duì)象的地址,
然后40110B是取了this指針?biāo)傅牡胤降闹?#xff08;也就是虛表指針的值),即虛表的地址傳給edx,
最后edx+4也就是虛表里面第二個(gè)元素的地址,然后[edx+4]即第二個(gè)元素的值(因?yàn)閿?shù)組元素是一個(gè)函數(shù)指針),也就是第二個(gè)虛函數(shù)ShowSpeak的地址。
由于虛函數(shù)采用間接調(diào)用機(jī)制,因此在使用父類(lèi)指針pPerson調(diào)用虛函數(shù)時(shí),沒(méi)有依照其作用域調(diào)用CPerson類(lèi)中定義的成員函數(shù)ShowSpeak
當(dāng)父類(lèi)中定義有虛函數(shù)時(shí),將會(huì)產(chǎn)生虛表。當(dāng)父類(lèi)的派生類(lèi)產(chǎn)生對(duì)象時(shí),將會(huì)調(diào)用子類(lèi)的構(gòu)造函數(shù)前優(yōu)先調(diào)用父類(lèi)構(gòu)造函數(shù),并以子類(lèi)對(duì)象的首地址作為this指針傳給父類(lèi)構(gòu)造函數(shù)。
在父類(lèi)構(gòu)造函數(shù)中,會(huì)先初始化子類(lèi)虛表指針為父類(lèi)的虛表首地址。此時(shí),如果在父類(lèi)構(gòu)造函數(shù)中調(diào)用虛函數(shù),雖然虛表指針屬于子類(lèi)對(duì)象,但指向的地址卻是父類(lèi)的虛表首地址,這是可判斷虛表所屬作用域相同,于是轉(zhuǎn)成直接調(diào)用,從而造成構(gòu)造函數(shù)內(nèi)的虛函數(shù)失效。
這里也就是在構(gòu)造函數(shù)里面調(diào)用了 虛函數(shù),會(huì)出現(xiàn) 多態(tài)的失效。
父類(lèi)構(gòu)造函數(shù)會(huì)在子類(lèi)構(gòu)造函數(shù)之前運(yùn)行,在執(zhí)行父類(lèi)構(gòu)造函數(shù)時(shí)將虛表指針修改為當(dāng)前類(lèi)的虛表指針,即父類(lèi)的虛表指針,導(dǎo)致虛函數(shù)特性失效。
如果父類(lèi)構(gòu)造函數(shù)內(nèi)部存在虛函數(shù)調(diào)用的話,這樣的順序能防止在子類(lèi)中構(gòu)造父類(lèi)時(shí),父類(lèi)會(huì)根據(jù)虛表錯(cuò)誤地調(diào)用子類(lèi)的成員函數(shù)。
大家心里會(huì)不會(huì)出現(xiàn)一個(gè)疑問(wèn):直接讓編譯器把構(gòu)造函數(shù)或析構(gòu)函數(shù)中的虛函數(shù)調(diào)用修改為直接調(diào)用方式,不就可以避免這類(lèi)問(wèn)題了嗎????
但是大家別忘了,程序員仍然可以自己編寫(xiě)其它成員函數(shù)間接調(diào)用本類(lèi)中聲明的其它虛函數(shù)。
舉個(gè)例子:
假設(shè)類(lèi)A中定義了成員函數(shù)f1()和虛函數(shù)f2(),而且類(lèi)B繼承類(lèi)A并重寫(xiě)f2()。根據(jù)前面的講解我們可以知道,在子類(lèi)B的構(gòu)造函數(shù)執(zhí)行前會(huì)調(diào)用父類(lèi)A的構(gòu)造函數(shù),此時(shí)如果在類(lèi)A的構(gòu)造函數(shù)中調(diào)用了f1(),顯然不會(huì)構(gòu)成多態(tài),編譯器會(huì)直接調(diào)用f1()的代碼,但是,如果在f1()中調(diào)用了f2(),此時(shí)就會(huì)產(chǎn)生間接調(diào)用的指令,形成多態(tài)。如果類(lèi)B的對(duì)象的虛表指針沒(méi)有更換為類(lèi)A的虛表指針,就會(huì)導(dǎo)致在訪問(wèn)類(lèi)B 的虛表后調(diào)用到類(lèi)B中的f2()函數(shù),而此時(shí)類(lèi)B的對(duì)象尚未完成構(gòu)造,其數(shù)據(jù)成員時(shí)不確定的,這時(shí)在f2()中引用類(lèi)B的對(duì)象中的數(shù)據(jù)成員是很危險(xiǎn)的。
同理,在析構(gòu)類(lèi)B的對(duì)象時(shí),會(huì)先執(zhí)行類(lèi)B的析構(gòu)函數(shù),然后執(zhí)行類(lèi)A 的析構(gòu)函數(shù)。如果在類(lèi)A的析構(gòu)函數(shù)中調(diào)用f1(),顯然不能構(gòu)成多態(tài),編譯器同樣會(huì)產(chǎn)生直接調(diào)用f1()的代碼。但是,如果f1()中又調(diào)用了f2(),此時(shí)會(huì)構(gòu)成多態(tài),如果這個(gè)對(duì)象的虛表指針沒(méi)有更換為類(lèi)A 的虛表指針,同樣也會(huì)導(dǎo)致訪問(wèn)虛表并調(diào)用類(lèi)B中的f2()。但是,此時(shí)B類(lèi)對(duì)象已經(jīng)執(zhí)行過(guò)析構(gòu)函數(shù)了,所以B類(lèi)中定義的數(shù)據(jù)已經(jīng)不可靠了,對(duì)其進(jìn)行操作是很危險(xiǎn)的
這也就是我們經(jīng)常說(shuō)的在構(gòu)造函數(shù)和析構(gòu)函數(shù)里面會(huì)發(fā)生虛表覆蓋
析構(gòu)總結(jié):
首先調(diào)用自身的析構(gòu)函數(shù),然后調(diào)用成員對(duì)象的析構(gòu)函數(shù),最后調(diào)用父類(lèi)的析構(gòu)函數(shù)。
在對(duì)象析構(gòu)時(shí),首先設(shè)置虛表指針為自身虛表,再調(diào)用自身的析構(gòu)函數(shù)。
如果有成員對(duì)象,則按申明順序以 倒序方式依次調(diào)用成員對(duì)象的析構(gòu)函數(shù)。
最后,調(diào)用父類(lèi)析構(gòu)函數(shù)。在調(diào)用父類(lèi)析構(gòu)函數(shù)的時(shí)候,會(huì)設(shè)置虛表指針為父類(lèi)自身的虛表。
構(gòu)造函數(shù)和析構(gòu)函數(shù)調(diào)用流程如下:
分析如下:
首先調(diào)用了父類(lèi)的構(gòu)造函數(shù),然后設(shè)置虛表指針為當(dāng)前類(lèi)的虛表首地址。而析構(gòu)函數(shù)中的順序卻與構(gòu)造函數(shù)相反,首先設(shè)置虛表指針為當(dāng)前類(lèi)的虛表首地址,然后調(diào)用父類(lèi)的析構(gòu)函數(shù)。其構(gòu)造和析構(gòu)過(guò)程如下:
構(gòu)造:基類(lèi)------>基類(lèi)的派生類(lèi)------->…………------->當(dāng)前類(lèi)
析構(gòu):當(dāng)前類(lèi)------>基類(lèi)的派生類(lèi)------->…………------->基類(lèi)
虛函數(shù)系列:
詳解虛函數(shù)的實(shí)現(xiàn)過(guò)程之初探虛表(1)
詳解虛函數(shù)的實(shí)現(xiàn)過(guò)程之單繼承(2)
詳解虛函數(shù)的實(shí)現(xiàn)過(guò)程之多重繼承(3)
詳解虛函數(shù)的實(shí)現(xiàn)過(guò)程之虛基類(lèi)(4)
詳解虛函數(shù)的實(shí)現(xiàn)過(guò)程之菱形繼承(5)
總結(jié)
以上是生活随笔為你收集整理的详解虚函数的实现过程之单继承(2)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 详解虚函数的实现过程之初探虚表(1)
- 下一篇: 详解虚函数的实现过程之多重继承(3)