C++ 多態的實現原理:虛表與晚綁定

C++ 多態

C++中多態是怎麼使用的?

用基類指針或引用 存儲/綁定 派生類 對象地址/對象,如果訪問的接口是虛函數,則訪問的是虛表中對應函數,而不只是基類函數。
可以看出,這種操作能夠實現一個接口多種實現,所以我們將它稱爲多態。

當給類寫了虛函數,類就會增加一個虛指針(虛函數表指針)成員,其地址爲類的首地址。並且編譯器會生成一個虛函數表,用於存儲類的所有虛函數的指針。
如圖:
在這裏插入圖片描述
代碼測試:測試是否爲上圖結構

#include <iostream>
using namespace std;
class Base
{
	public:
	virtual void fun(){
		cout << "function from Base" << endl;
	}
	void test()
	{
		void (*f)() = (void(*)())(*(int*)(*(int*)this));
		f();
	}
};
int main(){
	Base b;
	b.test();
} 

運行結果:
在這裏插入圖片描述

多態的實現原理:

早期綁定的概念:
C++編譯器在編譯的時候,要確定每個對象調用非虛函數的地址,並且將它綁定下來。而虛函數是在程序運行時通過虛指針訪問虛表再調用對應的虛函數。

我們以以下例子分析虛函數與普通函數的不同:

//測試代碼:
#include <iostream>
using namespace std;
class Base
{
	public:
	virtual void v_fun(){
		cout << "virtual function from Base" << endl;
	}
	void fun(){
		cout << "function from Base" << endl;
	}
};
class Derived
	:public Base
{
	public:
	virtual void v_fun(){
		cout << "virtual function from Derived" << endl;
	}
	void fun(){
		cout << "function from Derived" << endl;
	}
};
int main(){
	Derived d;
	Base* bptr = &d;
	bptr->v_fun();
 	bptr->fun();
} 

運行結果:
在這裏插入圖片描述

分析:
晚綁定:
當編譯器編譯到 bptr->v_fun()時,由於調用的是虛函數,編譯器並不會綁定函數地址,而是程序運行到這個地方時,訪問bptr所指向的虛指針再通過虛指針訪問虛表中對應的函數。由於虛表是Derived對象的虛表,所以訪問的是Derived對象的v_fun函數。
早綁定
當編譯器編譯到 bptr->fun()時,由於調用的是普通函數,所以編譯器會直接將其綁定至對象對應函數地址,注意:bptr作爲基類指針,不通過虛表則只能訪問到基類的成員函數,所以此時綁定的是基類函數的地址。

虛表的繼承:
當派生類繼承一個含有虛表的基類時,派生類會先繼承基類的虛表,然後改寫虛表中函數指針,如果有自己增加的虛函數,則在虛表中添加。但是需要我們注意的是,基類指針只能訪問基類已經實現的方法,所以就算派生類中添加了虛函數,通過基類指針也是訪問不到的。那爲什麼還要添加到虛表呢?因爲派生類也可以作爲基類被繼承。

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