虛數(shù)?虛繼承?虛函數(shù)? 從內(nèi)存分布角度解釋”虛“原理? 看到這個(gè)題目的時(shí)候,學(xué)法的我表情是這樣的。 然而,我樸素的法感情告訴我這樣的內(nèi)容難不倒我一個(gè)法律人。 幸好,我還有搜索引擎還有同組同學(xué)的幫助。 讓我們看看”虛“到底是什么~ 1.虛繼承 我們從目的和實(shí)現(xiàn)原理方面來看虛繼承 什么?虛繼承還有目的?來人啊,快把這要謀害我樸素法感情的人拿下! 1.1虛繼承的目的 虛繼承是解決C++多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會(huì)在子類中存在多份拷貝。 這將存在兩個(gè)問題: 其一,浪費(fèi)存儲(chǔ)空間。 其二,存在二義性問題。 通常可以將派生類對(duì)象的地址賦值給基類對(duì)象,實(shí)現(xiàn)的具體方式是,將基類指針指向繼承類(繼承類有基類的拷貝)中的基類對(duì)象的地址,但是多重繼承可能存在一個(gè)基類的多份拷貝,這就出現(xiàn)了二義性。 1.2一般繼承的實(shí)現(xiàn)原理 那么虛繼承是通過什么方式解決這兩個(gè)問題的呢? 我們要從內(nèi)存分布的角度來解釋原理。 因此需要知道一點(diǎn)visual studio開發(fā)人員命令行的知識(shí)。 我們首先從VS安裝目錄中打開開發(fā)人員命令行 進(jìn)入到對(duì)應(yīng)項(xiàng)目的目錄,然后輸入命令 cl -d1reportAllClassLayout yuan.cpp 這樣命令行界面就會(huì)給出該cpp里定義的所有類的內(nèi)存布局 如果想要特定類的內(nèi)存布局,可以輸入命令 cl -d1reportSingleClassLayout[類名] yuan.cpp 有了這樣的技術(shù)支持,我們就可以來探究虛繼承的內(nèi)存分布了。 1.2.1普通繼承的內(nèi)存布局 我們可以用visual studio新建項(xiàng)目,新建yuan.cpp,輸入如下代碼 打開開發(fā)人員命令行,進(jìn)入項(xiàng)目的目錄,輸入 cl -d1reportAllClassLayout yuan.cpp 可以得到我們定義的ABCD類的布局 可以看到,普通繼承時(shí) 類D包含的成員有(DataA,DataB,DataA,DataC,DataD) int DataA出現(xiàn)了兩次,因此類D的內(nèi)存大小為20 因此出現(xiàn)了內(nèi)存的浪費(fèi)和二義性問題。 1.2.2虛繼承的內(nèi)存布局 我們將繼承方式改為虛繼承,代碼如下 使用命令行查看類ABCD新的內(nèi)存布局 可以預(yù)見A的內(nèi)存布局并不會(huì)改變。 而B和C占用的內(nèi)存從8變?yōu)?2,因?yàn)槠渲卸嗔艘粋€(gè)指針成員vbptr,vbptr指向了vbtable,我們將vbptr稱為虛指針,將vbtable稱為虛表。 再來看看D的內(nèi)存分布 D占用的內(nèi)存從20變?yōu)?4,但D中的成員相比于普通繼承只有一個(gè)DataA,而多了兩個(gè)分別繼承自B和C的虛指針vbptr。 那么虛指針到底是什么呢? D的內(nèi)存布局其實(shí)已經(jīng)為我們提供了解答,我們可以看到兩個(gè)指針分別指向兩個(gè)虛表,虛表中記錄了vbptr與本類的偏移地址。 在本例子中,類B的vbptr指向了虛表D::$vbtable$@B@,虛表表明公共基類A的成員變量dataA距離類B開始處的位移為20,這樣就找到了成員變量dataA,類C的指針也對(duì)應(yīng)指向虛表,而虛表最終表示的偏移和B的虛表相同,這樣D中的成員就只有一個(gè)DataA,解決了二義性的問題。 但是我們可以看到,實(shí)際上使用虛繼承后BCD類的占用的內(nèi)存都變得更多了,并沒有解決內(nèi)存浪費(fèi)的問題。 這是因?yàn)槲覀冾怉的成員只有一個(gè)int類變量,如果A中的成員更復(fù)雜,占用更多的內(nèi)存,使用虛繼承時(shí)內(nèi)存的占用要比普通繼承少很多。 在寫完這些,我陷入的對(duì)自己人生的懷疑“我是誰,我在哪,我在干什么?然而這些都不能阻擋我接下來學(xué)習(xí)虛函數(shù)的熱情 2.虛函數(shù) 2.1虛函數(shù)的目的 在同一類中是不能定義兩個(gè)名字相同、參數(shù)個(gè)數(shù)和類型都相同的函數(shù)的,否則就是“重復(fù)定義”。 但是在類的繼承層次結(jié)構(gòu)中,在不同的層次中可以出現(xiàn)名字相同、參數(shù)個(gè)數(shù)和類型都相同而功能不同的函數(shù)。 人們提出這樣的設(shè)想,能否用同一個(gè)調(diào)用形式,既能調(diào)用派生類又能調(diào)用基類的同名函數(shù)。在程序中不是通過不同的對(duì)象名去調(diào)用不同派生層次中的同名函數(shù),而是通過指針調(diào)用它們。 例如,我們?cè)谖鰳?gòu)類的時(shí)候,一定是想要調(diào)用該類的析構(gòu)函數(shù),而不是基類的析構(gòu)函數(shù),否則有可能導(dǎo)致部分內(nèi)存沒有釋放。 C++中的虛函數(shù)就是用來解決這個(gè)問題的。虛函數(shù)的作用是允許在派生類中重新定義與基類同名的函數(shù),并且可以通過基類指針或引用來訪問基類和派生類中的同名函數(shù)。簡(jiǎn)單來說,虛函數(shù)是為了實(shí)現(xiàn)多態(tài)性。 2.2虛函數(shù)的實(shí)現(xiàn)原理 我們?nèi)詮膬?nèi)存分布的角度來探究虛函數(shù)的實(shí)現(xiàn) 2.2.1一般繼承 我們?nèi)源蜷_之前的項(xiàng)目,更改yuan.cpp的代碼如下 B類并沒有對(duì)繼承自A類函數(shù)進(jìn)行更改,我們調(diào)用VS開發(fā)者命令行 在項(xiàng)目目錄下執(zhí)行cl –d1reportAllClassLayout yuan.cpp 得到對(duì)應(yīng)AB類的內(nèi)存布局 可以看到,和虛繼承類似,有虛函數(shù)的類中的成員會(huì)多一個(gè)虛指針vfptr,虛指針指向虛函數(shù)表vftable。 在本例中,類A的虛指針指向A::$vftable@,這個(gè)虛表順序排列著我們?cè)陬怉中定義的f(),g(),h()函數(shù)。 由于我們沒有在子類B中對(duì)函數(shù)進(jìn)行定義的覆蓋,所以B的虛表中函數(shù)仍是A類中的f(),g(),h()函數(shù)。 現(xiàn)在我們更改代碼如下,在B類的定義中重新定義繼承的f()函數(shù)。 我們?cè)陬怋中對(duì)f()函數(shù)進(jìn)行了重新定義 再來看類AB的內(nèi)存布局 A類的虛表并不會(huì)變化,但B類的虛表發(fā)生了變化,具體是B類原本0位置的&A::f被&B::f覆蓋了。 因此如果我們聲明一個(gè)基類的指針,使指針指向子類,通過指針調(diào)用f()函數(shù),我們會(huì)調(diào)用B類虛表中的&B::f,從而實(shí)現(xiàn)了多態(tài)。 如果我們只對(duì)g()進(jìn)行重新定義,虛表會(huì)變成怎樣呢? 可以看出,虛表中函數(shù)的順序是與基類中定義順序相同,如果我們?cè)谧宇愔袑?duì)某個(gè)函數(shù)進(jìn)行重新定義,新的函數(shù)會(huì)覆蓋原本函數(shù)但并不會(huì)改變順序。 2.2.2多重繼承 假如子類繼承自多個(gè)父類,虛函數(shù)表又是如何實(shí)現(xiàn)的 更改代碼如下 我們定義了新的基類C,并在B中對(duì)父類的f()進(jìn)行覆蓋定義,并新定義虛函數(shù)k() 我們重新來看B的內(nèi)存布局 B中的成員理所當(dāng)然的有繼承自A和C的兩個(gè)虛指針。 繼承自A的虛指針指向虛函數(shù)表B::$vftable@A@ 其中的成員不僅有&B::f,&A::g,&A::h,還有B中新定義的虛函數(shù)。 而繼承自C的虛指針指向虛函數(shù)表C::$vftable@C@ 其中的成員為C的兩個(gè)虛函數(shù)。 也就是說B新定義的虛函數(shù)只會(huì)存放在繼承的第一個(gè)虛函數(shù)表中,并且在虛表中的位置要放到基類的虛函數(shù)之后。 3.總結(jié) 總的來說 虛繼承是為了解決多代繼承中的二義性和內(nèi)存浪費(fèi)的問題。 虛函數(shù)是為了解決多態(tài)性的問題。 二者都是通過虛指針和虛表來實(shí)現(xiàn)的。 當(dāng)然以上的話以我樸素的法感情看來他們依然是天書,但是我相信我對(duì)虛函數(shù)與虛繼承的理解已經(jīng)比之前好很多了。 另外預(yù)祝大家大作業(yè)寫bugDebug順利! by 伊藤優(yōu)香 and 孟令寰 |
|