最近在看《深度探索c++物件模型》在看到函式部分,遇到乙個關於多重繼承和函式指標如何實現多型的問題。如果你想看懂本篇內容,需要先理解c++中變數的記憶體模型,一點點的彙編基礎。類成員虛函式指標可以實現多型嘛?本篇部落格是基於微軟編譯器
我們有一段單繼承的**,並且定義了乙個函式指標,我們問題是:類成員虛函式指標在不同的物件呼叫情況下可不可以實現多型?
class
base};
class
imp:
public base};
intmain()
用子類imp和父類base的變數,我們都用base的指標指向這些成員,這個時候我們用test虛函式指標呼叫,結果是如下:
答案是可以的,也就是說。虛函式指標在不同的物件呼叫下也是可以支援多型。那麼它是怎麼做的呢?
虛函式指標如何支援多型
首先我們可以排除虛函式指標肯定不是直接指向函式位址的(非虛函式指標是直接指向函式位址)。
我們跟蹤下剛剛我們那段**在呼叫函式的時候做了什麼。
接下來我們會展示一波彙編**。
void
(base:
:*test)()
=&base:
:func;
base *b =
newbase
;(b-
>
*test)()
;//執行這段**時候發生了什麼
我們看當執行(b->*test)()時候發生了什麼
首先會把呼叫的物件放入乙個ecx的暫存器裡。然後呼叫虛函式指標test指向的函式,我們接下來看下這個函式就一條語句直接跳到叫base::`vcall的函式位址。
我們看base::`vcall中做了什麼
第一步,將ecx暫存器中的位址指向的內容取出到eax。如果了解c++記憶體模型的小夥伴都知道(不知道的可以看下文章)乙個物件中第一項是虛函式表指標。然後將eax(虛函式表)中(其實是第乙個虛函式)位址直接取出直接並且跳轉到該位址,然我們看下到跳到哪了。
到最後呼叫跳轉到base::func了,現在成功找到了需要呼叫的函式。
看不懂的,我用簡單圖總結下具體做了什麼
第1步:呼叫虛函式指標指向的函式,實際上呼叫的是base::`vcall這個函式
第2步:在vacll函式中將傳入的物件的虛函式表中的第一項取出,然後呼叫
可以看出我們的函式指標並不是指向實際的函式位址,而是先呼叫編譯器為我們設定的乙個函式,這個函式中我們會取出我們物件的虛函式表然後呼叫響應位置的虛函式。也就是說是根據傳入的虛函式表才會確定呼叫的是哪乙個函式。
看了單繼承的實現,那麼在多繼承的情況下有什麼特點呢?
為什麼在多繼承下情況比較複雜,因為在多繼承下我們會有兩個虛函式表,程式怎麼確定是哪乙個虛函式表呢?
以下面**為例項:
//基類p1
classp1}
;//基類p2
classp2}
;//多重繼承,先繼承p2,後繼承p1,p3記憶體結構會很複雜
classp3:
public p2,
public p1
//擁有自己的虛函式
virtual void
funcp3()
};intmain()
執行結果如下圖,還是準確定位到了相應的函式
給大家看下p3記憶體結構的圖
其中有兩個虛函式表,分別是從p2和p1中繼承下來的,但是對於我們要呼叫的funcp1這個函式它是在p1的虛函式表中記錄的。在我們定義p1的函式指標時候它並不知道自己在p3的記憶體結構中會是個什麼情況,所以它是怎麼確定哪乙個虛函式表呢?
實際上,程式並不會去挑哪個虛函式表,它只會去呼叫第乙個虛函式表。如果我們是函式指標不是在第乙個虛函式表,而是在第二個虛函式表,這個時候我們編譯器會自己計算需要偏移,把變數指標加上偏移(變相的確定了哪個虛函式表)。
大家注意在編譯期,p3是知道這個函式指標是p1型別的函式指標,p1的在p3中的記憶體也是可以確定的,也就是說這些完全可以確定,比那一起只要將p3成員指標偏移4(sizeof(p2))位就可以了。之後做的事就和單繼承一樣了。
我們同樣看下彙編後的**
編譯根據函式指標和物件指標的型別,直接計算出p3到p1的偏移量,把記憶體位址指向p1的偏移,然後和單繼承方式呼叫乙個叫p1::`vcall的函式就成功了。
c 繼承 多重繼承 多型性 虛函式
從繼承的角度來看,y 所繼承到的成員 x 的資料成員 的訪問屬性 x作為y 的成員,屬性是由繼承方式決定的,訪問屬性可由普通成員屬性推理理解 多重繼承的訪問屬性 可按照 上邊 繼承加遞迴的思想理解 多型性virtual this 編譯器幫助我們簡化操作,通過虛函式 方便多多 總結c 讓編譯器多做點工...
C 多重繼承的多型 Thunk
1 classa 2 3classb 4 5class c public a,publicb 6 7b b newc 8 9b foo virtual function 在為line 9實現多型的時候,b指向的不是c物件的開頭,而是其subobject b的開頭。因此在呼叫foo時,作為引數的thi...
C 多重繼承下的指標型別轉換
在c 中,指標的型別轉換是經常發生的事情,比如將派生類指標轉換為基類指標,將基類指標轉換為派生類指標。指標的本質其實就是乙個整數,用以記錄程序虛擬記憶體空間中的位址編號,而指標的型別決定了編譯器對其指向的記憶體空間的解釋方式。基於上面的理解,我們似乎可以得出乙個結論,c 中對指標進行型別轉換,不會改...