為了實現虛函式,c++ 使用了虛函式表來達到延遲繫結的目的。虛函式表在動態/延遲繫結行為中用於查詢呼叫的函式。
儘管要描述清楚虛函式表的機制會多費點口舌,但其實其本身還是比較簡單的。
首先,每個包含虛函式的類(或者繼承自的類包含了虛函式)都有乙個自己的虛函式表。這個表是乙個在編譯時確定的靜態陣列。虛函式表包含了指向每個虛函式的函式指標以供類物件呼叫。
其次,編譯器還在基類中定義了乙個隱藏指標,我們稱為 *__vptr , *__vptr 是在類例項建立時自動設定的,以指向類的虛函式表。 *__vptr 是乙個真正的指標,這和 *this 指標不同, *this 指標實際是乙個函式引數,使編譯器來達到自引用的目的。
結果就是,每個類物件都會多分配乙個指標的大小,並且 *__vptr 是被派生類繼承的。
如果你不清楚這些元件是怎麼配合運作的,看下面的例子:
class base因為這裡有 3 個類,編譯器會建立 3 個虛函式表。public:
virtual void function1() {};
virtual void function2() {};
class d1: public base
public:
virtual void function1() {};
class d2: public base
public:
virtual void function2() {};
然後編譯器會在使用了虛函式的最上層基類中定義乙個隱藏指標。儘管這個過程編譯器會自動處理,但我們還是通過下面的例子來說明指標新增的位置:
class base*__vptr 在類物件建立的時候會設定成指向類的虛函式表。例如,型別 base 被例項化的時候, *__vptr 就指向 base 的虛函式表。型別 d1 或者 d2 被例項化的時候, *__vptr 就指向 d1 或者 d2 的虛函式表。public:
functionpointer *__vptr;
virtual void function1() {};
virtual void function2() {};
class d1: public base
public:
virtual void function1() {};
class d2: public base
public:
virtual void function2() {};
現在我們來看下虛函式表是怎麼建立的。因為示例中每個類僅有 2 個虛函式,所以每個虛函式表會存放兩個函式指標(分別指向 function1() 和 function2() )。
base 物件的虛函式表最簡單。base 物件只能訪問 base 型別的成員,不能訪問 d1 或者 d2 的函式。所以 base 的虛函式表中的兩個指標分別指向 base::function1() 和 base::function2()。
d1 的虛函式表稍複雜點, d1 物件能夠訪問 d1 以及 base 的成員。 d1 重寫了 function1() ,但沒有重寫 function2() ,所以 d1 的虛函式表中的兩個指標分別指向 d1::function1()和 base::function2() 。
d2 的虛函式表同理 d1 ,包含了分別指向 base::function1() 和 d2::function2() 的指標。
考慮如果建立 d1 物件時會發生什麼:
int main()因為 d1 是 d1 型別物件, d1 有它自己的 *__vptr 指向 d1 型別的虛函式表。d1 d1;
現在建立乙個 base 型別指標 *dptr 指向 d1 :
int main()重點:d1 d1;
base *dptr = &d1;
return 0;
因為 dptr 是 base 型別指標,它只指向 d1 物件的 base 型別部分(即,指向 d1 物件中的 base 子物件),而 *__vptr 也在 base 型別部分。所以 dptr 可以訪問 base 型別部分中的 *__vptr 。同時,這裡注意, dptr->__vptr 指向的是 d1 的虛函式表,這是在 d1 初始化時就確定的。所以結果,儘管 dptr 是 base 型別指標,但它能夠訪問 d1 的虛函式表。
因此,當有呼叫 dptr->function1() 時,發生了什麼?
int main()首先,程式識別到 function1() 是乙個虛函式。d1 d1;
base *dptr = &d1;
dptr->function1();
return 0;
其次,程式使用 dptr->__vptr 獲取到了 d1 的虛函式表。
然後,它在 d1 的虛函式表中尋找可以呼叫的 function1() 版本,這裡是 d1::function1()。
因此, dptr->function1() 實際呼叫了 d1::function1() 。
通過虛函式表,編譯器和程式能夠確定呼叫什麼版本的虛函式,儘管使用的是指向/引用基類的指標或者引用。
呼叫虛函式會比呼叫非虛函式更慢,有以下幾個原因:
*__vptr
結果就是必須進行三次操作才能完成對函式的呼叫。但是對於現代計算機系統,這些額外操作增加的時間幾乎可以忽略不計。
另外,每個使用虛函式表的類都有 *__vptr 指標,從而每個類物件都會多乙個指標的空間。虛函式很強大,但是它確實產生了效能開銷。
還沒關注的小夥伴,可以長按關注一下:
C 中的虛函式表及虛函式執行原理詳解
為了實現虛函式,c 使用了虛函式表來達到延遲繫結的目的。虛函式表在動態 延遲繫結行為中用於查詢呼叫的函式。儘管要描述清楚虛函式表的機制會多費點口舌,但其實其本身還是比較簡單的。首先,每個包含虛函式的類 或者繼承自的類包含了虛函式 都有乙個自己的虛函式表。這個表是乙個在編譯時確定的靜態陣列。虛函式表包...
C 虛函式及虛函式表詳解
多型 的關鍵在於通過基類指標或引用呼叫乙個虛函式時,編譯時不確定到底呼叫的是基類還是派生類的函式,執行時才確定。include using namespace std class a virtual void func2 class b public a int main 在 32 位編譯模式下,程...
虛函式及虛函式表
虛函式及虛函式表 首先,我們要分清三大概念 過載 重寫 覆蓋 和重定義 一.函式過載 1 在相同的作用域內 無繼承關係,只在乙個類內進行宣告 2 進行多個函式宣告 3 多個函式的函式名相同,引數列表不同 可以是型別不同 引數型別不同 傳參順序不同 4 函式的返回值型別可以相同,可以不同。不能僅依靠函...