1.1Function Template初窺
所謂的function templates是指由參數化手段表現一整個族羣的function。
一個function templates可以表示一族(一整羣)functions,其表現和一般的function並無二致,只是其中某些元素在編寫時尚未確定,換而言之,那些[尚未確定的元素]被[參數化]了。
1.1.1定義Template
下面的function templates傳回兩個數值中的較大者:
inline T const & max( T const & a, T const & b )
{
return a < b ? b : a
}
這一份template定義式代表了一整族functions,他們的作用都是傳回a和b兩參數中的較大者。兩個參數的型別都尚未確定,我們說它"template parameter T"。如你所見,template parameters必須以如此形式加以宣告:
template<以逗號分隔的參數列>
上述例子中,參數列就是typename T。請注意,例子中的[<]和[>]在這裏被當作尖括號使用。關鍵字typename引入了一個所謂的type parameter(型別參數)------這是目前爲止C++程式中最常使用的一種template parameter,另還存在其他種類的template parameter(如:nontype parameter,[非型別參數]),這個以後討論。
此處的型別參數是T,你也可以使用其他任何標識符號(identifier)來表示型別參數,但習慣寫成T(譯註:代表Type)。Type parameter可標識任何型別,在function templates被呼叫時,經由傳輸具體型別而使T得以被具體指定。你可以使用任何型別,只要它支持T所要完成的操作。本列中型別T必須支援operator<以比較兩值大小。
由於歷史因素,你也可以使用關鍵字class代替關鍵字typename來定義一個type parameter。關鍵字typename是C++發展晚期才引進的,在此之前只能經由關鍵字class引入type parameter。關鍵字class目前依然可以用。因此template max()也可以被寫成如下等價形式:
inline T const & max(T const & a, T const & b)
{
return a < b ? b : a;
}
就語義而言,前後兩者毫無區別。即便使用關鍵字class,你還是可以把任意型別(包括non-class型別)當作實際的template arguments。但是這麼寫可能帶來一些誤導(讓人誤以爲T必須是class型別),所以最好還是使用關鍵字typename。請注意,這和class的型別宣告並不是同一回事:宣告type parameters時我們不能把關鍵字typename換成關鍵字struct。
1.1.2使用Template
以下程式示範如何使用max() function template:
#include <string>
#include "max.hpp"
int main()
{
int i = 42;
std::cout<<"max(7,i): "<<::max(7,i)<<std::endl;
double f1 = 3.4;
double f2 = -6.7;
std::cout<<"max(f1,f2): "<<::max(f1,f2)<<std::endl;
std::string s1 = "mathematics";
std::string s2 = "math";
std::cout<<"max(s1,s2): "<<::max(s1,s2)<<std::endl;
}
程序呼叫了max()三次。第一次參數是兩個int,第二次參數是兩個double,最後一次參數是兩個std::string。每一次max()均比較兩值取大者。程序運行結果爲:
max(f1,f2): 3.4
max(s1,s2): mathematics
注意程序對max()的三次呼叫都加了前綴符號"::",以遍確保被喚起的是我們在全域名空間(global namespace)中定義的max()。標準程序庫內也有一個std::max() template,可能會在某些情況下被喚起,或在呼叫時引起模棱兩可(ambiguity,歧義性,如果某個引數的型別定義於namespace std中,例如string,根據C++搜尋規則,::max()和std::max()都會被找到,那就會引起歧義性)。
一般而言,templates不會被編譯爲[能夠處理任意型別]的單一實物(entity),而是被編譯爲多個個別實物,每一個處理某一特定型別。([一份實物,適用所有型別],理論上成立,實際不可行。畢竟所有語言規則都奠基於[將會產生出不同實物]的概念all language rules are based on the concept that different entities are generated)。因此,針對三個型別,max()被編譯成三個實物。
int i = 42;
...max(7,i)...
使用的是[以int爲template parameter T]的funciton template,語義上等同於呼叫以下函數:
{
return a < b ? b : a;
}
以具體型別替換template parameters的過程稱爲[具現化](instantiation,或稱[實體化])。過程中會產生template的一份實體(instance)。不巧的是,instantiation和instance這兩個術語在OO(面向對象)編程領域中有其他含義,通常用來表示一個class的具體物件(concrete object)。
注意,只要function template被使用,就會自動引發具現化過程。程序員沒有必要個別申請具現過程。
類似情況,另兩次對max()的呼叫被具現化爲:
const std::string & max(std::string const &, std::string const &);
如果試圖以某個型別來具現化function template,而該型別並未支援function template中用到的操作,就會導致編譯錯誤。例如:
...
max(c1,c2); //編譯期出錯
實際上,template會被編譯兩次:
1.不具現化,只是對template程序代碼進行語法檢查以發現諸如[缺少分號;]等等的語法錯誤。
2.具現化,編譯器檢查template程序代碼中的所有呼叫是否合法,諸如[未授支援函數調用]便會在這個階段被檢查出來。
這會導致一個嚴重問題:當function template被運用而引發具現化過程時,某些時候編譯器需要用到template的原始定義。一般情況下,對普通的(non-template)functions而言,編譯和連結兩步驟是各自獨立的,編譯器只檢查各個functions的宣告式是否和呼叫式相符,然後template的編譯破壞了這個規則。眼下我們用最簡單的解法:把template程序代碼以inline形式寫在表頭檔(header)中。