C++虛函數表解析(2)

多重繼承(無虛函數覆蓋)

下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關係。注意:子類並沒有覆蓋父類的函數。

 

對於子類實例中的虛函數表,是下面這個樣子:

 

我們可以看到:

1)每個父類都有自己的虛表。(這樣做就是爲了解決不同的父類類型的指針指向同一個子類實例,而能夠調用到實際的函數)

2)子類的成員函數被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的)

多重繼承(有虛函數覆蓋)

下面我們再來看看,如果發生虛函數覆蓋的情況。

下圖中,我們在子類中覆蓋了父類的f()函數。

 

下面是對於子類實例中的虛函數表的圖:

 

我們可以看見,三個父類虛函數表中的f()的位置被替換成了子類的函數指針。這樣,我們就可以任一靜態類型的父類來指向子類,並調用子類的f()了。如:

Derive d;

Base1 *b1 = &d;

Base2 *b2 = &d;

Base3 *b3 = &d;

b1->f(); //Derive::f()

b2->f(); //Derive::f()

b3->f(); //Derive::f()

b1->g(); //Base1::g()

b2->g(); //Base2::g()

b3->g(); //Base3::g()

安全性

每次寫C++的文章,總免不了要批判一下C++。這篇文章也不例外。通過上面的講述,相信我們對虛函數表有一個比較細緻的瞭解了。水可載舟,亦可覆舟。下面,讓我們來看看我們可以用虛函數表來乾點什麼壞事吧。

一、通過父類型的指針訪問子類自己的虛函數

我們知道,子類沒有重載父類的虛函數是一件毫無意義的事情。因爲多態也是要基於函數重載的。雖然在上面的圖中我們可以看到Base1的虛表中有Derive的虛函數,但我們根本不可能使用下面的語句來調用子類的自有虛函數:

Base1 *b1 = new Derive();

b1->f1(); //編譯出錯

任何妄圖使用父類指針想調用子類中的未覆蓋父類的成員函數的行爲都會被編譯器視爲非法,所以,這樣的程序根本無法編譯通過。但在運行時,我們可以通過指針的方式訪問虛函數表來達到違反C++語義的行爲。(關於這方面的嘗試,通過閱讀後面附錄的代碼,相信你可以做到這一點)

二、訪問non-public的虛函數

另外,如果父類的虛函數是private或是protected的,但這些非public的虛函數同樣會存在於虛函數表中,所以,我們同樣可以使用訪問虛函數表的方式來訪問這些non-public的虛函數,這是很容易做到的。

如:

 #include <iostream>
using namespace std;

class Base
{
private:
 virtual void f()
 {
  cout << "Base::f" << endl;
 }
};

class Derive : public Base
{
};

typedef void(*Fun)(void);

int main()
{
 Derive d;
 Fun pFun = (Fun)*((int*)*(int*)(&d)+0);
 pFun();
 return 0;
}

輸出是Base:f

結束語

C++這門語言是一門Magic的語言,對於程序員來說,我們似乎永遠摸不清楚這門語言揹着我們在幹了什麼。需要熟悉這門語言,我們就必需要了解C++裏面的那些東西,需要去了解C++中那些危險的東西。不然,這是一種搬起石頭砸自己腳的編程語言。

 

轉載:http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章