1. 程式人生 > >C++物件模型之虛擬函式實現原理

C++物件模型之虛擬函式實現原理

在C++中,多型(polymorphism)的意思是,用基類的指標或者引用,定址出一個派生類物件。而虛擬函式(virtual member function)是多型的基礎,這也是面向物件程式設計迷人之處。現在剛好有時間,就寫一下自己對C++在單一繼承情況下如何實現虛擬函式的膚淺認識。

一旦一個類有一個虛擬函式,那麼一定會建立一個虛表(virtual table),虛表裡面有所有虛擬函式的地址。虛表是該類所有物件共享的,每個物件都有一個虛指標(vptr),指向虛表。虛指標的設定與重置,是由類的建構函式與解構函式以及複製運算子(copy assignment)自動完成的。

虛表是虛擬函式的基礎。表格有很多槽(slot),通常第一個槽位存放的是每一個類所關聯的型別資訊(type_info object),用來支援執行時型別識別(RTTI)。

class Demo{
public:
	Demo();
	virtual ~Demo();
	virtual void f1();
	virtual void f2();
	void f3();
	static void f4();
private:
	static int ival;
	long lval;
};

圖1 Demo類的物件記憶體佈局

每個物件內部都會儲存非靜態成員變數以及虛指標,其它成員都會在物件外部儲存,這些不是這次要討論的內容。

在C++中,虛擬函式在編譯器間獲知,而且這些虛擬函式的地址是固定不變的,執行期不可能新增或者被替換。為了找到函式的地址,每一個虛擬函式都會有一個在虛表裡面的索引號。

如果繼承基類的過程中,派生類決定不改寫某個虛擬函式,那麼它就會繼承基類這個函式的實體(定義),此時,在這個派生類的虛表中存放的就是基類的該函式。否則,如果派生類重寫了基類的某個虛擬函式,那麼派生類的虛表中存放的就會是對此虛擬函式重新定義的實體。如下圖所示。

class Base{
public:
	virtual void x();
	virtual void y();
	int ival;
};
//單一繼承
class Derived: public Base{
public:
	void x();
	virtual void z();
	long lval;
};

圖2 單一繼承情況下的虛表

特別要注意的是:

1)如果是繼承基類宣告的虛擬函式實體,那麼該函式實體就會被拷貝到派生類虛表相對應的槽位上。

2)如果是使用自己的函式實體,也就是改寫基類的虛擬函式,那麼也必須把這個函式實體地址放到相對應的槽位上。

3)如果是新增加一個虛擬函式,那麼虛表就會增加一個slot,並把新的虛擬函式實體放進這裡。

所以,假設對於上面的例子,我們有這樣的用法:

ptr->x();

我們不知道ptr是Base還是Dervied指標,也就無法由ptr取得該物件的虛表,但是,我知道每一個x()函式的地址都會放在#1槽位上,所以,編譯器會將該呼叫轉換成

(*ptr->vptr[1])(ptr);

上面的表示式中,唯一需要在執行期才能知道的東西是,槽位1是哪個類虛表的,一旦知道了,那麼便可呼叫相應的函式體了。

就寫到這。

多重繼承與虛擬繼承下,虛擬函式的實現就沒那麼容易了。下次有空寫一下。