最近在看C++的一些相關的機制,再加上剛看了陳皓大神的早期關於虛函數表的博客,便自己動手通過編程瞭解了下虛函數表的原理。
前言
c++是通過虛函數來實現多態的的機制。我們可以通過將父類的指針指向子類的實例,如Base b = new Derive()
,如此一來,如果子類Derive
中重載了父類中的一個函數h()
,那麼調用b->h()
等同於調用Derive d
, d->h()
。我們這裏來剖析下虛函數表的實現機制。
1 虛函數表的內部結構
我們可以通過編程來對虛函數表的內部結構來一窺究竟。如下代碼:
#include <iostream>
using namespace std;
class Base{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
我們構造了一個基類,定義了三個虛函數f()
,g()
,h()
,分別輸出對應的描述。我們通過以下代碼來得到該基類的虛函數表地址,和虛函數表中各個函數的地址。
int main() {
Base1 b;
cout << "虛函數表地址:" << (long*)(&b)+1 << endl;
cout << "虛函數表-第一個函數地址:" << ((long*)*(long*)(&b)) << endl;
cout << "虛函數表-第二個函數地址:" << ((long*)*(long*)(&b)+1) << endl;
cout << "虛函數表-第三個函數地址:" << ((long*)*(long*)(&b)+2) << endl;
}
我的機器是64位系統,其中函數指針大小爲16位,因此使用long*
強轉。運行結果如下:
虛函數表地址:0x7fffb023d7a8
虛函數表-第一個函數地址:0x466c88
虛函數表-第二個函數地址:0x466c90
虛函數表-第三個函數地址:0x466c98
爲了進一步驗證這些函數地址的有效性,使用一個函數指針pFun
來指向這些函數:
int main() {
typedef void(*Fun)(void);
Base1 b;
Fun pFun = NULL;
pFun = (Fun)*((long*)*(long*)(&b));
pFun();
pFun = (Fun)*((long*)*(long*)(&b)+1);
pFun();
pFun = (Fun)*((long*)*(long*)(&b)+2);
pFun();
}
結果如下:
Base::f
Base::g
Base::h
由此我們可知該基類的虛函數表的結構應該是這樣:
2 一般繼承無函數重寫的情況
我們來分析單繼承有子類的情況,而且該子類有自己的虛函數,並無對父類的函數進行重寫。數據結構如下:
class Base{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class Derive{
public:
virtual void f1() { cout << "Derive::f1" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
通過編程驗證可知,其父類的虛函數表沒有變化,其子類的虛函數表是這樣:
3 一般繼承有函數重寫的情況
如果其子類有函數重寫的情況,如下數據結構:
class Base{
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
class Derive{
public:
void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
virtual void h1() { cout << "Derive::h1" << endl; }
};
可以看出子類重寫了父類的 f 函數,這時候再看子類的虛函數表結構:
可以看出子類的f函數覆蓋了父類的f函數的位置,其他位置不變。
不妨編程驗證一下:
int main() {
typedef void(*Fun)(void);
Base b;
Derive d;
Fun pFun = NULL;
pFun = (Fun)*(long*)*((long*)(&d));
pFun();
}
結果:
Derive::f
4 多重繼承無函數重寫的情況
待續,,,