模板的實例化指函數模板(類模板)生成模板函數(模板類)的過程。對於函數模板而言,模板實例化之后,會生成一個真正的函數。而類模板經過實例化之后,只是完成了類的定義,模板類的成員函數需要到調用時才會被初始化。模板的實例化分為隱式實例化和顯示實例化。
對函數模板的使用而言,分為兩種調用方式,一種是顯示模板實參調用(顯示調用),一種是隱式模板實參調用(隱式調用)。對於類模板的使用而言,沒有隱式模板實參和顯式模板實參使用的說法,因為類模板的使用必須顯示指明模板實參。各個概念請勿混淆。
1.隱式實例化
1.1模板隱式實例化的定義
這是相對於模板顯示實例化而言。在使用模板函數和模板類時,不存在指定類型的模板函數和模板類的實體時,由編譯器根據指定類型參數隱式生成模板函數或者模板類的實體稱之為模板的隱式實例化。
1.2函數模板隱式實例化
函數模板隱式實例化指的是在發生函數調用的時候,如果沒有發現相匹配的函數存在,編譯器就會尋找同名函數模板,如果可以成功進行參數類型推演,就對函數模板進行實例化。
還有一種簡介調用函數的情況,也可以完成函數模板的實例化。所謂的簡介調用是指將函數入口地址傳給一個函數指針,通過函數指針完成函數調用。如果傳遞給函數指針不是一個真正的函數,那么編譯器就會尋找同名的函數模板進行參數推演,進而完成函數模板的實例化。參考如下示例。
#include <iostream> using namespace std; template <typename T> void func(T t){ cout<<t<<endl; } void invoke(void (*p)(int)){ int num=10; p(num); } int main(){ invoke(func); }
程序成功運行並輸出10。
1.3類模板隱式實例化
類模板隱式實例化指的是在使用模板類時才將模板實例化,相對於類模板顯示實例化而言的。考察如下程序。
#include <iostream> using namespace std; template<typename T>class A{ T num; public: A(){ num=T(6.6); } void print(){ cout<<"A'num:"<<num<<endl; } }; int main(){ A<int> a; //顯示模板實參的隱式實例化 a.print(); }
程序輸出結果:A’num:6。
2. 顯示實例化
2.1模板顯示實例化的定義
顯示實例化也稱為外部實例化。在不發生函數調用的時候講函數模板實例化,或者在不適用類模板的時候將類模板實例化稱之為模板顯示實例化。
2.2函數模板的顯示實例化
對於函數模板而言,不管是否發生函數調用,都可以通過顯示實例化聲明將函數模板實例化,格式為:
template [函數返回類型] [函數模板名]<實際類型列表>(函數參數列表)
例如:
template void func<int>(const int&);
2.3類模板的顯示實例化
對於類模板而言,不管是否生成一個模板類的對象,都可以直接通過顯示實例化聲明將類模板實例化,格式為:
template class [類模板名]<實際類型列表>
例如:
template class theclass<int>;
3.函數模板調用方式
3.1隱式模板實參調用
在發生函數模板的調用時,不顯示給出模板參數而經過參數推演,稱之為函數模板的隱式模板實參調用(隱式調用)。如:
template <typename T> void func(T t){ cout<<t<<endl; } func(5);//隱式模板實參調用
3.2顯示模板實參調用
在發生函數模板的調用時,顯示給出模板參數而不需要經過參數推演,稱之為函數模板的顯示模板實參調用(顯示調用)。
顯示模板實參調用在參數推演不成功的情況下是有必要的。考察如下程序。
#include <iostream> using namespace std; template <typename T> T Max(const T& t1,const T& t2){ return (t1>t2)?t1:t2; } int main(){ int i=5; //cout<<Max(i,'a')<<endl; //無法通過編譯 cout<<Max<int>(i,'a')<<endl; //顯示調用,通過編譯 }
直接采用函數調用Max(i,’a’)會產生編譯錯誤,因為i和’a’具有不同的數據類型,無法從這兩個參數中進行類型推演。而采用Max< int>(i,’a’)調用后,函數模板的實例化不需要經過參數推演,而函數的第二個實參也可以由char轉換為int型,從而成功完成函數調用。
編程過程中,建議采用顯示模板實參的方式調用函數模板,這樣提高了代碼的可讀性,便於代碼的理解和維護。
4.模板特化
4.1模板特化的定義
模板特化不同於模板的實例化,模板參數在某種特定類型下的具體實現稱為模板的特化。模板特化有時也稱之為模板的具體化,分別有函數模板特化和類模板特化。
4.2函數模板特化
函數模板特化是在一個統一的函數模板不能在所有類型實例下正常工作時,需要定義類型參數在實例化為特定類型時函數模板的特定實現版本。查看如下例子。
#include <iostream> using namespace std; template<typename T> T Max(T t1,T t2){ return (t1>t2)?t1:t2; } typedef const char* CCP; template<> CCP Max<CCP>(CCP s1,CCP s2){ return (strcmp(s1,s2)>0)?s1:s2; } int main(){ //調用實例:int Max<int>(int,int) int i=Max(10,5); //調用顯示特化:const char* Max<const char*>(const char*,const char*) const char* p=Max<const char*>("very","good"); cout<<"i:"<<i<<endl; cout<<"p:"<<p<<endl; }
程序正常編譯運行結果:
i:10
p:very
在函數模板顯示特化定義(Explicit Specialization Definition)中,顯示關鍵字template和一對尖括號<>,然后是函數模板特化的定義。該定義指出了模板名、被用來特化模板的模板實參,以及函數參數表和函數體。在上面的程序中,如果不給出函數模板Max< T>在T為const char*時的特化版本,那么在比較兩個字符串的大小時,比較的是字符串的起始地址的大小,而不是字符串的內容在字典序中先后次序。
4.2.1使用函數重載替代函數模板特化
除了定義函數模板特化版本外,還可以直接給出模板函數在特定類型下的重載形式(普通函數)。使用函數重載可以實現函數模板特化的功能,也可以避免函數模板的特定實例的失效。例如,把上面的模板特化可以改成如下重載函數。
typedef const char* CCP; CCP Max(CCP s1,CCP s2){ return (strcmp(s1,s2)>0)?s1:s2; }
程序運行結果和使用函數模板特化相同。但是,使用普通函數重載和使用模板特化還是有不同之處,主要表現在如下兩個方面:
(1)如果使用普通重載函數,那么不管是否發生實際的函數調用,都會在目標文件中生成該函數的二進制代碼。而如果使用模板的特化版本,除非發生函數調用,否則不會在目標文件中包含特化模板函數的二進制代碼。這符合函數模板的“惰性實例化”准則。
(2)如果使用普通重載函數,那么在分離編譯模式下,應該在各個源文件中包含重載函數的申明,否則在某些源文件中就會使用模板函數,而不是重載函數。
4.3類模板特化
類模板特化類似於函數模板的特化,即類模板參數在某種特定類型下的具體實現。考察如下代碼。
#include <iostream> using namespace std; template<typename T>class A{ T num; public: A(){ num=T(6.6); } void print(){ cout<<"A'num:"<<num<<endl; } }; template<>class A<char*>{ char* str; public: A(){ str="A' special definition "; } void print(){ cout<<str<<endl; } }; int main(){ A<int> a1; //顯示模板實參的隱式實例化 a1.print(); A<char*> a2; //使用特化的類模板 A2.print(); }
程序輸出結果如下:
A’num:6
A’ special definition
總結:
隱式實例化指的是:在使用模板之前,編譯器不生成模板的聲明和定義實例。只有當使用模板時,編譯器才根據模板定義生成相應類型的實例。如:int i=0, j=1;swap(i, j); //編譯器根據參數i,j的類型隱式地生成swap<int>(int &a, int &b)的函數定義。Array<int> arVal;//編譯器根據類型參數隱式地生成Array<int>類聲明和類函數定義。
顯式實例化:當顯式實例化模板時,在使用模板之前,編譯器根據顯式實例化指定的類型生成模板實例。如前面顯示實例化(explicit instantiation)模板函數和模板類。其格式為:template typename function<typename>(argulist);template class classname<typename>;顯式實例化只需聲明,不需要重新定義。編譯器根據模板實現實例聲明和實例定義。
顯示具體化:對於某些特殊類型,可能不適合模板實現,需要重新定義實現,此時可以使用顯示具體化(explicite specialization)。顯示實例化需重新定義。格式為:template<> typename function<typename>(argu_list){...};template<> class classname<typename>{...};