虛函數和虛函數表


多態是由虛函數實現的,而虛函數主要是通過虛函數表(V-Table)來實現的。

如果一個類中包含虛函數(virtual修飾的函數),那么這個類就會包含一張虛函數表,虛函數表存儲的每一項是一個虛函數的地址。如下圖:

這個類的每一個對象都會包含一個虛指針(虛指針存在於對象實例地址的最前面,保證虛函數表有最高的性能),這個虛指針指向虛函數表。

注:對象不包含虛函數表,只有虛指針,類才包含虛函數表,派生類會生成一個兼容基類的虛函數表。

  • 原始基類的虛函數表

  下圖是原始基類的對象,可以看到虛指針在地址的最前面,指向基類的虛函數表(假設基類定義了3個虛函數)

  • 單繼承時的虛函數(無重寫基類虛函數

假設現在派生類繼承基類,並且重新定義了3個虛函數,派生類會自己產生一個兼容基類虛函數表的屬於自己的虛函數表

  Derive class 繼承了 Base class 中的三個虛函數,准確的說,是該函數實體的地址被拷貝到 Derive類的虛函數表,派生類新增的虛函數置於虛函數表的后面,並按聲明順序存放

  • 單繼承時的虛函數(重寫基類虛函數

現在派生類重寫基類的x函數,可以看到這個派生類構建自己的虛函數表的時候,修改了base::x()這一項,指向了自己的虛函數。

  • 多重繼承時的虛函數(Derived ::public Base1,public Base2)

這個派生類多重繼承了兩個基類base1,base2,因此它有兩個虛函數表。

  

  它的對象會有多個虛指針(據說和編譯器相關),指向不同的虛函數表。

  多重繼承時指針的調整:

Derive b;
Base1* ptr1 = &b;   // 指向 b 的初始地址
Base2* ptr2 = &b;   // 指向 b 的第二個子對象

因為 Base1 是第一個基類,所以 ptr1 指向的是 Derive 對象的起始地址,不需要調整指針(偏移)。

因為 Base2 是第二個基類,所以必須對指針進行調整,即加上一個 offset,讓 ptr2 指向 Base2 子對象。

當然,上述過程是由編譯器完成的。

Base1* b1 = (Base1*)ptr2;
b1->y();                   // 輸出 Base2::y()
Base2* b2 = (Base2*)ptr1;
b2->y();                   // 輸出 Base1::y()

其實,通過某個類型的指針訪問某個成員時,編譯器只是根據類型的定義查找這個成員所在偏移量,用這個偏移量獲取成員。由於 ptr2 本來就指向 Base2 子對象的起始地址,所以b1->y()調用到的是Base2::y(),而 ptr1 本來就指向 Base1 子對象的起始地址(即 Derive對象的起始地址),所以b2->y()調用到的是Base1::y()

  • 虛繼承時的虛函數表

  虛繼承的引入把對象的模型變得十分復雜,除了每個基類(MyClassA和MyClassB)和公共基類(MyClass)的虛函數表指針需要記錄外,每個虛擬繼承了MyClass的父類還需要記錄一個虛基類表vbtable的指針vbptr。MyClassC的對象模型如圖4所示。

  

   虛基類表每項記錄了被繼承的虛基類子對象相對於虛基類表指針的偏移量。比如MyClassA的虛基類表第二項記錄值為24,正是MyClass::vfptr相對於MyClassA::vbptr的偏移量,同理MyClassB的虛基類表第二項記錄值12也正是MyClass::vfptr相對於MyClassA::vbptr的偏移量。

 

 

 

編譯時若基類中有虛函數,編譯器為該的類創建一個一維數組的虛表,存放是每個虛函數的地址。基類和派生類都包含虛函數時,這兩個類都建立一個虛表。構造函數中進行虛表的創建和虛表指針的初始化。在構造子類對象時,要先調用父類的構造函數,初始化父類對象的虛表指針,該虛表指針指向父類的虛表。執行子類的構造函數時,子類對象的虛表指針被初始化,指向自身的虛表。每一個類都有虛表。虛表可以繼承,如果子類沒有重寫虛函數,那么子類虛表中仍然會有該函數的地址,只不過這個地址指向的是基類的虛函數實現。派生類的虛表中虛函數地址的排列順序和基類的虛表中虛函數地址排列順序相同。當用一個指針/引用調用一個函數的時候,被調用的函數是取決於這個指針/引用的類型。即如果這個指針/引用是基類對象的指針/引用就調用基類的方法;如果指針/引用是派生類對象的指針/引用就調用派生類的方法,當然如果派生類中沒有此方法,就會向上到基類里面去尋找相應的方法。這些調用在編譯階段就確定了。當涉及到多態性的時候,采用了虛函數和動態綁定,此時的調用就不會在編譯時候確定而是在運行時確定。不在單獨考慮指針/引用的類型而是看指針/引用的對象的類型來判斷函數的調用,根據對象中虛指針指向的虛表中的函數的地址來確定調用哪個函數。

參考:https://www.cnblogs.com/LUO77/p/5771237.html#4340025


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM