c++ 虛函數表解析
對C++ 了解的人都應該知道虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其內容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,所以,當我們用父類的指針來操作一個子類的時候,這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。
這裡我們著重看一下這張虛函數表。C++的編譯器應該是保證虛函數表的指針存在於對象實例中最前面的位置(這是為了保證取到虛函數表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。 這意味著我們通過對象實例的地址得到這張虛函數表,然後就可以遍歷其中函數指針,並調用相應的函數。
可以將虛函數表看做一個二維數組,其中每個一維數組的首地址存儲在對象的實例之中。
來看個例子,通過實例得到虛函數表地址,進而訪問虛函數。
/*
version: 1.0
author: hellogiser
blog: http://www.cnblogs.com/hellogiser
date: 2014/9/29
*/
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base1
{
public:
Base1()
{
}
virtual void f()
{
cout << "Base1::f" << endl;
}
virtual void g()
{
cout << "Base1::g" << endl;
}
virtual void h()
{
cout << "Base1::h" << endl;
}
};
typedef void(*Fun)(void);
void test()
{
Base1 obj;
Fun pFun;
int **pVtab = (int **)(&obj);
// base1
pFun = (Fun)pVtab[0][0];
pFun();
pFun = (Fun)pVtab[0][1];
pFun();
pFun = (Fun)pVtab[0][2];
pFun();
}
int main()
{
test();
return 0;
}
/*
Base1::f
Base1::g
Base1::h
*/
圖解如下
在上面這個圖中,虛函數表的最後多加了一個結點,這是虛函數表的結束結點,就像字符串的結束符“/0”一樣,其標志了虛函數表的結束。這個結束標志的值在不同的編譯器下是不同的。
下面,將分別說明“無覆蓋”和“有覆蓋”時的虛函數表的樣子。沒有覆蓋父類的虛函數是毫無意義的。之所以要講述沒有覆蓋的情況,主要目的是為了給一個對比。在比較之下,我們可以更加清楚地知道其內部的具體實現。
(1)一般繼承(無虛函數覆蓋)
下面,再讓我們來看看繼承時的虛函數表是什麼樣的。假設有如下所示的一個繼承關系:
請注意,在這個繼承關系中,子類沒有重載任何父類的函數。那麼,在派生類的實例中,其虛函數表如下所示:
對於實例:Derive d; 的虛函數表如下:
我們可以看到下面幾點:
1)虛函數按照其聲明順序放於表中。
2)父類的虛函數在子類的虛函數前面。
代碼驗證如下
/*
version: 1.0
author: hellogiser
blog: http://www.cnblogs.com/hellogiser
date: 2014/9/29
*/
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base1
{
public:
Base1()
{
}
virtual void f()
{
cout << "Base1::f" << endl;
}
virtual void g()
{
cout << "Base1::g" << endl;
}
virtual void h()
{
cout << "Base1::h" << endl;
}
};
class Derived: public Base1
{
virtual void f1()
{
cout << "Derived::f1" << endl;
}
virtual void g1()
{
cout << "Derived::g1" << endl;
}
virtual void h1()
{
cout << "Derived::h1" << endl;
}
};
typedef void(*Fun)(void);
void test()
{
Derived obj;
Fun pFun;
int **pVtab = (int **)(&obj);
// base1
pFun = (Fun)pVtab[0][0];
pFun();
pFun = (Fun)pVtab[0][1];
pFun();
pFun = (Fun)pVtab[0][2];
pFun();
// derived
pFun = (Fun)pVtab[0][3];
pFun();
pFun = (Fun)pVtab[0][4];
pFun();
pFun = (Fun)pVtab[0][5];
pFun();
}
int main()
{
test();
return 0;
}
/*
Base1::f
Base1::g
Base1::h
Derived::f1
Derived::g1
Derived::h1
*/