類模板機制
是對一批僅僅成員數(shù)據(jù)類型不同的類的抽象,程序員只要為這一批類所組成的整個類家族創(chuàng)建一個類模板,給出一套程序代碼,就可以用來生成多種具體的類。
類模板用于實現(xiàn)類所需數(shù)據(jù)的類型參數(shù)化。
類模板在表示如數(shù)組、表、圖等數(shù)據(jù)結(jié)構(gòu)顯得特別重要,這些數(shù)據(jù)結(jié)構(gòu)的表示和算法不受所包含的元素類型的影響。
類模板基本語法舉例:
模板的編譯過程
什么是編譯單元:一個編譯單元(translation unit)是指一個.cpp文件以及它所#include的所有.h文件,.h文件里的代碼將會被擴展到包含它的.cpp文件里,然后編譯器編譯該.cpp文件為一個.obj文件,并且本身包含的就已經(jīng)是二進制碼,但是不一定能夠執(zhí)行,因為并不保證其中一定有main函數(shù)。
什么是分離式編譯:一個項目由若干個源文件共同實現(xiàn),而每個源文件(.cpp)單獨編譯成目標文件(.obj),最后將所有目標文件連接起來形成單一的可執(zhí)行文件的過程。
下面舉一個例子來說明:
test.h文件內(nèi)容如下:
test.cpp文件內(nèi)容如下:
main.cpp文件內(nèi)容如下:
test. cpp和main.cpp各自被編譯成不同的.obj文件,在main.cpp中,調(diào)用了func函數(shù),然而當編譯器編譯main.cpp時,它僅僅知道的只是main.cpp中所包含的test.h文件中的一個關(guān)于void func();的聲明,所以,編譯器將這里的f看作外部連接類型,func的實現(xiàn)代碼實際存在于test.cpp所編譯成的test.obj中。在main.obj中對f的調(diào)用只會生成一行call指令,鏈接器負責在其它的.obj中尋找func的實現(xiàn)代碼,找到以后將call func這個指令的調(diào)用地址換成實際的func的函數(shù)進入點地址。然而,對于模板,模板函數(shù)的代碼其實并不能直接編譯成二進制代碼,其中要有一個“實例化”的過程。舉個例子(將模板的聲明和實現(xiàn)分離):test.h文件內(nèi)容如下:
test.cpp文件內(nèi)容如下:
main.cpp文件內(nèi)容如下:
編譯器在#1處并不知道A<int>::f的定義,因為它不在test.h里面,于是編譯器只好寄希望于鏈接器,希望它能夠在其他.obj里面找到A<int>::func的實例,在本例中就是test.obj,然而,后者中真有A<int>::func的二進制代碼嗎?NO?。。∫驗镃++標準明確表示,當一個模板不被用到的時侯它就不該被實例化出來,test.cpp中用到了A<int>::func了嗎?沒有?。∷詫嶋H上test.cpp編譯出來的test.obj文件中關(guān)于A::f一行二進制代碼也沒有,于是鏈接器就傻眼了,只好給出一個鏈接錯誤。但是,如果在test.cpp中寫一個函數(shù),其中調(diào)用A<int>::func,則編譯器會將其實例化出來,鏈接器就能夠完成任務。
模板的二次編譯
非模板類在編譯的時候就會被實例化出代碼,但編譯模板類則不是如此,C++標準明確表示當一個模板不被用到的時侯它就不該被實例化出來。
當編譯到用模板類特例定義對象的代碼時,如A<int> a; 此時編譯器才會生成對應實例化類的二進制代碼,即第二次編譯。
第二次編譯的時候如果該編譯單元能訪問到模板類的實現(xiàn)代碼,則好說,否則只能等到鏈接,如果其它模塊也沒有實例化過該代碼,則會鏈接出錯。
解決辦法
模板類聲明在test.h中,定義在main.cpp中,調(diào)用在main.cpp中,則能運行成功。
模板類聲明在test.h中,定義在test.h中,調(diào)用在main.cpp中,則能運行成功,因為在預處理階段會對頭文件展開。