對於模板,包括模板類與模板函式,它們的**其實並不是直接翻譯成二進位制**,它要求有乙個「具體化」(例項化)的過程,舉個例子:
template
void funa(t t)
int main()
也就是說,如果在main函式中,沒有呼叫過funa函式的話,那麼在main.obj中就找不到關於funa的任意二進位制**,如果呼叫了funa函式,那麼在main.obj就會找到關於funa函式的具體化二進位制**。
然而具體化要求編譯器知道模板的定義。看看下面的例子(將模板實現與宣告分離):
test.h檔案
template
class a
test.cpp檔案
include 「test.h」
template
void a::funa()
main.cpp檔案
include 「test.h」
int main()
由於編譯器不知道a::funa函式的定義,那麼它就只好希望鏈結器能夠在test.obj裡面找到它的定義了。然而,test.obj裡面真的就有a::funa的二進位制**嗎?答案是沒有的,因為根據c++標準,當乙個模板不被用到,那它就不應該被具體化,也就是說test.cpp裡面如果沒有用到a::funa的話,a::funa函式的二進位制**就不會被編譯到test.obj檔案中去。由於test.cpp沒有呼叫過a::funa函式,所以在test.obj中就不會有a::funa的二進位制**。於是當鏈結器進行鏈結時,它找不到a::funa的二進位制,所以就會給出乙個鏈結錯誤。但是,當在test.cpp中寫乙個函式呼叫a::funa函式,那麼在test.obj中就會有a::funa這個符號位址,於是鏈結就能夠完成。
這個問題要怎麼解決呢?
下面就講乙個c++模板**的組織方式——包含模式。
大多數c/c++程式設計師像下面這樣組織他們的非模板**:
1)類和其他型別全部放在標頭檔案中,這些標頭檔案具有.hpp(或者.h, .h, .hh, .hxx)副檔名。
2)對於全域性變數和(非內聯)函式,只有宣告放在標頭檔案中,而定義放在點c檔案中,這些檔案具有.cpp(或者.c, .c, .cc, .cxx)副檔名。
這種組織方式工作的很好:它使得在程式設計時可以方便地訪問所需的型別定義,並且避免了來自鏈結器的「變數或函式重複定義」的錯誤。
由於以上組織方式約定的影響,模板程式設計新手往往會犯乙個同樣的錯誤。下面這一小段程式反映了這種錯誤。就像對待「普通**」那樣,我們在標頭檔案中定義模板:
// basics/myfirst.hpp
(#)ifndef myfirst_hpp
(#)define myfirst_hpp
// declaration of template
template
void print_typeof (t const&);
(#)endif // myfirst_hpp
print_typeof()宣告了乙個簡單的輔助函式用來列印一些型別資訊。函式的定義放在點c檔案中:
// basics/myfirst.cpp
(#)include
(#)include
(#)include 「myfirst.hpp」
// implementation/definition of template
template
void print_typeof (t const& x)
這個例子使用typeid操作符來列印乙個字串,這個字串描述了傳入的引數的型別資訊。 最後,我們在另外乙個點c檔案中使用我們的模板,在這個檔案中模板宣告被#include:
// basics/myfirstmain.cpp
(#)include 「myfirst.hpp」
// use of the template
int main()
大部分c++編譯器(compiler)很可能會接受這個程式,沒有任何問題,但是鏈結器(linker)大概會報告乙個錯誤,指出缺少函式print_typeof()的定義。
這個錯誤的原因在於,模板函式print_typeof()的定義還沒有被具現化(instantiate)。為了具現化乙個模板,編譯器必須知道哪乙個定義應該被具現化,以及使用什麼樣的模板引數來具現化。不幸的是,在前面的例子中,這兩組資訊存在於分開編譯的不同檔案中。因此,當我們的編譯器看到對print_typeof()的呼叫,但是沒有看到此函式為double型別具現化的定義時,它只是假設這樣的定義在別處提供,並且建立乙個那個定義的引用(鏈結器使用此引用解析)。另一方面,當編譯器處理myfirst.cpp時,該檔案並沒有任何指示表明它必須為它所包含的特殊引數具現化模板定義。
標頭檔案中的模板
解決上面這個問題的通用解法是,採用與我們使用巨集或者內聯函式相同的方法:我們將模板的定義包含進宣告模板的標頭檔案中。對於我們的例子,我們可以通過將#include 「myfirst.cpp」新增到myfirst.hpp檔案尾部,或者在每乙個使用我們的模板的點c檔案中包含myfirst.cpp檔案,來達到目的。當然,還有第三種方法,就是刪掉myfirst.cpp檔案,並重寫myfirst.hpp檔案,使它包含所有的模板宣告與定義:
// basics/myfirst2.hpp
(#)ifndef myfirst_hpp
(#)define myfirst_hpp
(#)include
(#)include
// declaration of template
template
void print_typeof (t const&);
// implementation/definition of template
template
void print_typeof (t const& x)
(#)endif // myfirst_hpp
這種組織模板**的方式就稱作包含模式。經過這樣的調整,你會發現我們的程式已經能夠正確編譯、鏈結、執行了。
從這個方法中我們可以得到一些觀察結果。最值得注意的一點是,這個方法在相當程度上增加了包含myfirst.hpp的開銷。在這個例子中,這種開銷並不是由模板定義自身的尺寸引起的,而是由這樣乙個事實引起的,即我們必須包含我們的模板用到的標頭檔案,在這個例子中是和。你會發現這最終導致了成千上萬行的**,因為諸如這樣的標頭檔案也包含了和我們類似的模板定義。
這在實踐中確實是乙個問題,因為它增加了編譯器在編譯乙個實際程式時所需的時間。我們因此會在以後的章節中驗證其他一些可能的方法來解決這個問題。但無論如何,現實世界中的程式花一小時來編譯鏈結已經是快的了(我們曾經遇到過花費數天時間來從原始碼編譯的程式)。
拋開編譯時間不談,我們強烈建議如果可能盡量按照包含模式組織模板**。
另乙個觀察結果是,非內聯模板函式與內聯函式和巨集的最重要的不同在於:它並不會在呼叫端展開。相反,當模板函式被具現化時,會產生此函式的乙個新的拷貝。由於這是乙個自動的過程,編譯器也許會在不同的檔案中產生兩個相同的拷貝,從而引起鏈結器報告乙個錯誤。理論上,我們並不關心這一點:這是編譯器設計者應當關心的事情。實際上,大多數時候一切都運轉正常,我們根本就不用處理這種狀況。然而,對於那些需要建立自己的庫的大型專案,這個問題偶爾會顯現出來。
最後,需要指出的是,在我們的例子中,應用於普通模板函式的方法同樣適用於模板類的成員函式和靜態資料成員,以及模板成員函式。
我在實際工作中遇到的問題是,如果可以通過在標頭檔案中用typedef的方式,來進行例項化,這樣在使用時,只要包含標頭檔案就可以了。
c 模板類和模板函式的使用
include includeusing namespace std 類模板 template class student 模板類成員函式的定義 如果在類體內實現函式,則不用加template 如果在類體外實現函式,則必須每個函式前都要加templatetemplate student studen...
C 模板函式與類模板
函式模板提供了一種函式行為,該函式行為可以用多種不同的型別進行呼叫,也即是說,函式模板代表乙個函式家族。includetemplatet max t const a,t const b 無參建構函式 stack stackconst 拷貝建構函式 stack operator stackconst ...
C 模板類和模板函式
參考 c 中模板使用詳解 c 模板詳解 為了避免因過載函式定義不全面而帶來的呼叫錯誤,引入了模板機制 模板是c 支援引數化多型的工具,使用模板可以使使用者為類或者函式宣告一種一般模式,使得類中的某些資料成員或者成員函式的引數 返回值取得任意型別。類模板template 返回型別 函式名 引數列表 說...