簡單地說,每個含有虛函數(不管是其自己的,仍是繼承而來的)的類都至少有一個與之對應的虛函數表,其中存放着該類全部的虛函數對應的函數指針。例:html
其中:git
提示:爲了描述方便,本文在探討對象內存佈局時,將忽略內存對齊對佈局的影響。github
從編譯器的角度來講,B的虛函數表很好構造,D的虛函數表構造過程相對複雜。下面給出了構造D的虛函數表的一種方式(僅供參考):ide
提示:該過程是由編譯器完成的,所以也能夠說:虛函數替換過程發生在編譯時。函數
如下面的程序爲例:佈局
編譯器只知道pb是B*類型的指針,並不知道它指向的具體對象類型 :pb可能指向的是B的對象,也可能指向的是D的對象。指針
但對於「pb->bar()」,編譯時可以肯定的是:此處operator->的另外一個參數是B::bar(由於pb是B*類型的,編譯器認爲bar是B::bar),而B::bar和D::bar在各自虛函數表中的偏移位置是相等的。htm
不管pb指向哪一種類型的對象,只要可以肯定被調函數在虛函數中的偏移值,待運行時,可以肯定具體類型,並能找到相應vptr了,就能找出真正應該調用的函數。對象
提示:本人曾在「C/C++雜記:深刻理解數據成員指針、函數成員指針」一文中提到:虛函數指針中的ptr部分爲虛函數表中的偏移值(以字節爲單位)加1。blog
B::bar是一個虛函數指針, 它的ptr部份內容爲9,它在B的虛函數表中的偏移值爲8(8+1=9)。
當程序執行到「pb->bar()」時,已經可以判斷pb指向的具體類型了:
當一個類繼承多個類,且多個基類都有虛函數時,子類對象中將包含多個虛函數表的指針(即多個vptr),例:
其中:D自身的虛函數與B基類共用了同一個虛函數表,所以也稱B爲D的主基類(primary base class)。
虛函數替換過程與前面描述相似,只是多了一個虛函數表,多了一次拷貝和替換的過程。
虛函數的調用過程,與前面描述基本相似,區別在於基類指針指向的位置可能不是派生類對象的起始位置,以以下面的程序爲例:
本文不討論菱形繼承的情形,我的以爲:菱形繼承的複雜度遠大於它的使用價值,這也是C++讓人又愛又恨的緣由之一。
若是想要深刻研究,能夠參考:Itanium C++ ABI。