C++多态的原理(虚函数指针和虚函数表)
C++多態的原理 (虛函數指針和虛函數表)
- 1.虛函數指針和虛函數表
- 2.繼承中的虛函數表
- 2.1單繼承中的虛函數表
- 2.2多繼承中的虛函數表
- 3.多態的原理
- 4.總結
1.虛函數指針和虛函數表
以下代碼:
問類實例化出的對象占幾個字節?
結果顯而易見 sizeof(a)=4,因為成員函數存放在公共的代碼段, 所以只計算成員變量m_a(int型)所占字節的大小。
當我們將成員函數定義為虛函數時,結果卻出現了不同的情況:
我們注意到當成員函數定義為虛函數時,同一個類的實例化對象大小變為了8個字節。多出來的4個字節是怎么回事呢?
另外在對象a中還多出了一個void**類型名為_vfptr的變量。它是一個二級指針, 指針在32位平臺中占4字節, 所以這里的結果是8(m_a的4字節+_vfptr的4字節), 那么_vfptr到底是個什么東西? 類中有了虛函數之后才有了_vfptr, 它們之間到底有著什么聯系?
當一個類中有虛函數時,編譯期間就會為這個類分配一片連續的內存 (虛表vftable),來存放虛函數的地址。類中只保存著指向虛表的指針 (虛函數表指針_vfptr) ,當這個類實例出對象時,每個對象都會有一個虛函數表指針_vfptr 。虛函數其實和普通函數一樣,存放在代碼段。
一個含有虛函數的類中都至少都有一個虛函數表,因為虛函數的地址要被放到虛函數表中,那么派生類中這個表放了些什么呢?我們接著往下分析。
針對上面的代碼我們做出以下改造:
1.我們增加一個派生類去繼承基類
2.基類中重寫Func1
3.派生類再增加一個虛函數Func2和一個普通函數Func3
通過觀察和測試,我們發現了以下幾點問題:
答:虛函數存在虛表,虛表存在對象中。上面的回答是錯的。注意虛表存的是虛函數指針,不是虛函數,虛函數和普通函數一樣的,都是存在代碼段的,只是他的指針又存到了虛表中。另外對象中存的不是虛表,存的是虛表指針。那么虛表存在哪的呢?實際我們去驗證一下會發現在vs下是存在代碼段的。
總結:
當一個類中有虛函數時, 在編譯期間,就會為這個類分配一片連續的內存 (虛表vftable), 來存放虛函數的地址, 類中只保存著指向虛表的指針 (虛函數指針_vfptr) , 虛函數其實和普通函數一樣, 存放在代碼段。當這個類實例出對象時, 每個對象都會有一個虛函數表指針_vfptr 。虛表本質上是一個在編譯時就已經確定好了的void* 類型的指針數組 。
注意 : 虛函數表為了標志結尾, 會在虛表最后一個元素位置保存一個空指針。所以看到的虛表元素個數比實際虛函數個數多一個。
2.繼承中的虛函數表
在有虛函數的類被繼承后, 虛表也會被拷貝給派生類。編譯器會給派生類新分配一片空間來拷貝基類的虛表, 將這個虛表的指針給派生類, 而并不是沿用基類的虛表。在發生虛函數的重寫時, 重寫的是派生類為了拷貝基類虛表新創建的虛表中的虛函數地址。 虛表為所有這個類的對象所共享,是通過給每個對象一個虛表指針_vfptr共享到的虛表。
2.1單繼承中的虛函數表
所以, 重寫實際上就是在繼承基類虛表時, 把基類的虛函數地址修改為派生類虛函數的地址。
#include<iostream> using namespace std;class Base {int m_a; public:virtual void func() {cout << "類A的func" << endl;}virtual int func1() {cout << "類A的func1" << endl;return 0;} }; class Derive :public Base { public:virtual void func() {cout << "類B的func" << endl;}virtual void func2() {cout << "類B的func2" << endl;} }; int main() {Base a1;Base a2;Derive b;system("pause");return 0; }基類對象a1,a2中,虛表中的地址相同(虛函數func()和func1()的地址),是因為虛表為類的所有對象共享,是通過給每個對象一個虛表指針_vfptr共享到的虛表。
派生類對象b,繼承了基類的虛表,虛函數指針_vptr卻和a1,a2的不同,這是因為編譯器新分配了一片空間來拷貝基類的虛表。派生類中重寫了虛函數func(),由于被重寫的虛函數地址會在繼承虛表時被修改為派生類函數的地址。所以派生類的虛表中func()的地址被改變了。
我們還發現,派生類中的虛函數func2()卻沒有出現在派生類中的虛表中。按理來說, 如果派生類中新增了虛函數, 則會加繼承的虛表后面。其實這個虛函數地址是存在的,我們可以發現箭頭所指的虛函數表vftable[4],其中應該有四個元素,除去虛表中多出的一個空指針,還有另外三個func(),func1(),func2(),只不過這里沒有顯示func2()。我們可以通過調用監視窗口來查看func2()。
2.2多繼承中的虛函數表
派生類繼承了兩張虛表。派生類中重寫了func()函數,派生類自己拷貝基類虛表中含func()的地址都被改變了。
第一張虛表vftable[4],說明其中含有四個元素,除了funcA()、func()、多出來的一個空指針外,還有派生類中新的虛函數funcC()。調用監視窗口可以看到,派生類中新增的虛函數funcC()被加在了派生類拷貝基類的第一張虛表的后面。
3.多態的原理
多態的構成條件:
原理: 利用虛函數可以重寫的特性, 當一個有虛函數的基類有多個派生類時, 通過各個派生類對基類虛函數的不同重寫, 實現指向派生類對象的基類指針或基類引用調用同一個虛函數, 去實現不同功能的特性。抽象來說就是, 為了完成某個行為, 不同的對象去完成時會產生多種不同的狀態。
4.總結
單繼承:
①先將基類中的虛表內容拷貝一份到派生類虛表中。
②如果派生類重寫了基類中某個虛函數,派生類中的虛函數地址替換虛表中基類的虛函數地址。
③派生類自己新增加的虛函數按其在派生類中的聲明次序增加到派生類虛表的最后。
多繼承:
①繼承的多個基類中有多張虛表, 派生類會全部拷貝下來, 成為派生類的多張虛表 。
②如果派生類重寫了基類中的某個虛函數,所有含有這個函數的基類虛表都會被重寫 (改的是派生類自己拷貝的基類虛表, 并不是基類自己的虛表)。
③派生類自己新增加的虛函數加在派生類拷貝的第一張虛表后
總結
以上是生活随笔為你收集整理的C++多态的原理(虚函数指针和虚函数表)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 语言的分类
- 下一篇: Coffee and Coursewor