虛函式實現機制

2022-05-17 01:59:02 字數 2864 閱讀 3606

說到虛函式的實現方法,我們就不得不說到動態聯編(dynamic binding)和靜態聯編(static binding)。靜態聯編意味著編譯器能夠直接將識別符號和儲存的實體地址聯絡在一起。每乙個函式都有乙個唯一的實體地址,當編譯器遇到乙個函式呼叫時,它將用乙個機械語言說明來替代函式呼叫,用來告訴cpu跳至這個函式的位址,然後對此函式進行操作。這個過程是在編譯過程中完成的(注:呼叫的函式在編譯時必須能夠確定),所以靜態聯編也叫前期聯編(early binding)。但是,如果使用哪個函式不能在編譯時確定,則需要採用動態聯編的方式,在程式執行時在呼叫函式,所以動態聯編也叫後期聯編(late binding)。

在c++繼承多型中,如若要在派生類中重新定義基類的方法,則要把它宣告為虛函式,並且用指標或者引用去呼叫它的方法,實現動態聯編,否則編譯器預設的將是靜態聯編。小看一下這個例子:

example1:

#include

using namespace std;

class a

//注意此處的函式不是虛函式

};class b : public a

};int main (void)

執行結果:

a

a

原因:編譯器預設為靜態聯編方式,所以函式f(),在編譯過程中就已經定死了,在子類中儘管你重新定義了f()的方法,但是編譯器不知道應該呼叫哪個函式,所以就只會用的靜態聯編時的函式方法。

example 2:

#include

using namespace std;

class a

//注意此處宣告了虛函式

};class b : public a

};int main (void)

執行結果:

abb

b先總結一下兩個程式,若基類不宣告為虛函式,則在a   a = b時,或者 a 

對比兩個結果就能很清楚的看到虛函式的作用。但是它具體的實現原理是什麼呢?

c++採用了動態聯編的一種特殊形式去實現虛函式,稱為虛函式表。虛函式表是一張函式查詢表,用以解決以動態聯編方式呼叫函式。它為每個可以被類物件呼叫的虛函式提供乙個入口,這樣當我們用基類的指標或者引用來操作子類的物件時,這張虛函式表就提供了編譯器實際呼叫的函式。虛函式表其實是儲存了為類物件進行宣告的虛函式位址。當我們建立乙個類物件時,編譯器會自動的生成乙個指標*__vptr(乙個隱藏指標),該指標指向這個類中所有虛函式的位址表。(實際上,虛函式表就是乙個函式位址陣列表。),請注意,*__vptr和*this指標不同,*this是乙個被編譯器用作解決自引用的函式引數,而*__vptr則是乙個真正的指標。

每乙個類,不管是基類還是子類都有乙個自己的virtual table,而*__vptr也是被繼承過來的。

我們再看乙個例子:

example:

#include

using namespace std;

class a

//f()被宣告為虛函式

virtual void g()    //g()被宣告為虛函式

};

class b : public a

};class c : public a

};int main (void)

執行結果:

b』s f()

a』s g()

a』s f()

c』s g()

這個程式就能夠反映出虛函式是怎樣通過virtual table實現的,自己繪了一張圖:應該能比較清楚的反映情況(借鑑於learncpp.com)

1、c++實現多型的方法

其實很多人都知道,虛函式在c++中的實現機制就是用虛表和虛指標,但是具體是怎樣的呢?從more effecive c++其中一篇文章裡面可以知道:是每個類用了乙個虛表,每個類的物件用了乙個虛指標。具體的用法如下:

class a

;class b : public a

;//a,b的實現省略

因為a有virtual void f(),和g(),所以編譯器為a類準備了乙個虛表vtablea,內容如下:

a::f 的位址

a::g 的位址

b因為繼承了a,所以編譯器也為b準備了乙個虛表vtableb,內容如下:

a::f 的位址

b::g 的位址

注意:因為b::g是重寫了的,所以b的虛表的g放的是b::g的入口位址,但是f是從上面的a繼承下來的,所以f的位址是a::f的入口位址。

然後某處有語句 b bb;的時候,編譯器分配空間時,除了a的int a,b的成員int b;以外,還分配了乙個虛指標vptr,指向b的虛表vtableb,bb的布局如下:

vptr : 指向b的虛表vtableb

int a: 繼承a的成員

int b: b成員

當如下語句的時候:

a *pa = &bb;

pa的結構就是a的布局(就是說用pa只能訪問的到bb物件的前兩項,訪問不到第三項int b)

那麼pa->g()中,編譯器知道的是,g是乙個宣告為virtual的成員函式,而且其入口位址放在**(無論是vtalbea表還是vtalbeb表)的第2項,那麼編譯器編譯這條語句的時候就如是轉換:call *(pa->vptr)[1](c語言的陣列索引從0開始哈~)。

這一項放的是b::g()的入口位址,則就實現了多型。(注意bb的vptr指向的是b的虛表vtableb)

另外要注意的是,如上的實現並不是唯一的,c++標準只要求用這種機制實現多型,至於虛指標vptr到底放在乙個物件布局的**,標準沒有要求,每個編譯器自己決定。我以上的結果是根據g++ 4.3.4經過反彙編分析出來的。

參考:

虛函式實現機制

c 中的虛函式的作用主要是實現了多型的機制。關於多型,簡而言之就是用父型別別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。這種技術可以讓父類的指標有 多種形態 這是一種泛型技術。虛函式的作用是實現動態聯編,也就是在程式的執行階段動態地選擇合適的成員函式。當程式發現虛函式名前的關鍵...

虛函式的實現機制

將函式宣告為virtual時,在背後發生了什麼呢?編譯器在編譯的時候,發現animal類中有虛函式,此時編譯器會為每個包含虛函式的類建立乙個虛表 即vtable 該表是乙個一維陣列,在這個陣列中存放每個虛函式的位址。對於例1 2的程式,animal和fish類都包含了乙個虛函式breathe 因此編...

C 虛函式實現機制

看完之後,對c 中的虛函式實現機制算的上是恍然大悟,但是個人感覺博文中有幾點不足之處,現在一一枚舉,以下語言僅僅代表個人看法 1.定位虛表的方式 大家都知道含有虛函式的類的例項裡面前4個位元組是虛函式指標占用的記憶體,裡面填充的是虛函式表的位址號。原博文中通過乙個long型別的物件取得前四個位元組的...