漫談C++爲何不支持模板虛函數

首先,咱們要區分模板類虛函數和模板虛函數,話很少說,先上代碼:ide

模板類虛函數函數

template<class T>
class A
{
public:
	virtual ~A(){}
	virtual void foo(T &t){}
};

template<class T, class R>
class B : public A<T>
{
	R *r;
public:
	void foo(T &t) override{}
};

模板虛函數設計

class A
{
public:
	virtual ~A(){}
	template<class T>
	virtual void foo(T &t){}
};

class B : public A
{
public:
	template<class T>
	void foo(T &t) override{}
};

顯然模板虛函數是編譯不過的,至於爲何,咱們能夠深究至C++多態的實現原理,就能知道爲何C++不容許定義模板虛函數了。指針

咱們知道C++的多態是經過虛表實現的,對於含有虛函數的類,會爲其定義一個虛表,每一個實例化的對象都有一個指向該虛表的指針,因此一樣的類,含有虛函數的類的實例大小比不含虛函數的多上一個指針的大小,虛表裏爲每一個虛函數維護着一條跳轉記錄,這些跳轉地址在編譯期就被肯定了,存放在類定義模塊的數據段中,在程序運行期是不可修改的。那麼這跟模板虛函數有什麼關係呢?code

讓咱們瞭解一點關於模板的特性,C++對於模版的處理,首先,模版並不算一種類型,在編譯時,編譯器只對已經實例化的模板類生成對應的模板類代碼,假如這些類中定義的有模板類虛函數,則對每一個實例化的模板類型建立一個虛表,這就是第一種狀況---模板類虛函數,是可行的。對象

如今再看看模板虛函數,爲何不可行,就拿上面的代碼講:編譯器

A是一個類型,它含有模板虛函數,雖然是虛函數,可是函數的符號並不肯定,由於咱們不知道模板T是一個什麼類型,對於從沒調用過這個模板函數的狀況下,這個模板虛函數甚至都不會實例化,那麼就至關於沒有虛函數了。那麼爲了實現模板虛函數,咱們姑且認爲它就是含有虛函數,因此A應該有一張虛表,可是A的虛函數符號並不肯定,要根據當前調用的狀況來肯定,A的這個模板虛函數到底實例化了幾個類型,那麼對於每一個類型的虛函數都添加一個虛表記錄,這樣看起來,實現模板虛函數貌似是可行的,可是這也只僅限於單個文件編譯成可執行文件的狀況下。編譯

咱們都知道C++編譯中間是有幾個步驟的,預處理、編譯和連接,每一個cpp或c文件都會被編譯成目標文件,而後這些目標文件在經過連接生成可執行文件。那麼考慮一下這種狀況,假如如今我有兩個cpp文件分別是x.cpp和y.cpp,上面的模板虛函數,我在x.cpp文件中實例化了模板

void foo(int& t);
void foo(float& t);

而在y.cpp中實例化了class

void foo(int& t);
void foo(bool& t);

那麼x.o和y.o中的A類的虛表都含有兩天記錄,可是函數符號卻並不同,那麼爲了實現模板虛函數,進行連接的時候就須要對虛表合併去重了,先拋去實現代價的問題,從理論上看起來的確是可行的。

然而事情並非到此爲止了,咱們知道目標文件不僅是能夠連接成可執行文件,還能夠連接成靜態庫和動態庫,對於靜態庫,再進行連接的時候和普通連接差異不大,可是動態庫就沒有那麼好運了。

考慮這樣的一種狀況,在動態庫裏面定義了上面的模板函數,並且實例化了

void foo(int& t);
void foo(float& t);

這兩個虛函數,B類型的虛表在動態連接庫已經肯定,兩條記錄,可是在咱們的程序裏剛好調用了上面的模板虛函數,這時候實例化了

void foo(bool& t);

那麼此時爲了繼續下去,就得修改動態連接庫中B類的虛表了,爲它添加一條記錄,很顯然是行不通的。至於爲何行不通,拋開程序段的可讀寫的問題不談,若是真的可修改,那麼這個類型的每一個實例均可能會守到其它實例的影響了,與類的設計原則相悖了。

至此爲何模板虛函數爲何行不通已經很明顯了。

本人才疏學淺,憑本身的理解發表一點理解,有什麼不正確之處還各路大神敬請批評指正。

相關文章
相關標籤/搜索