本文針對C++函數模板與類模板進行了較為詳盡的執行個體解析,有助於協助讀者加深對C++函數模板與類模板的理解。具體內容如下:
泛型程式設計(Generic Programming)是一種編程範式,通過將型別參數化來實現在同一份代碼上操作多種資料類型,泛型是一般化並可重複使用的意思。泛型程式設計最初誕生於C++中,目的是為了實現C++的STL(標準模板庫)。
模板(template)是泛型程式設計的基礎,一個模板就是一個建立類或函數的藍圖或公式。例如,當使用一個vector這樣的泛型型別或者find這樣的泛型函數時,我們提供足夠的資訊,將藍圖轉換為特定的類或函數。
一、函數模板
一個通用的函數模板(function template)就是一個公式,可用來產生針對特定類型或特定值的函數版本。模板定義以關鍵字template開始,後面跟一個模板參數列表,列表中的多個模板參數(template parameter)以逗號分隔。模板參數表示在類或函數定義中用到的類型或值。
1、型別參數
一個模板型別參數(type parameter)表示的是一種類型。我們可以將型別參數看作類型說明符,就像內建類型或類類型說明符一樣使用。型別參數前必須使用關鍵字class 或typename:
template <typename T> // typename和class一樣的 T function(T* p) { T tmp = *p; // 臨時變數類型為T //... return tmp; // 傳回值類型為T }
關鍵字typename和class是一樣的作用,但顯然typename比class更為直觀,它更清楚地指出隨後的名字是一個類型名。
編譯器用模板類型實參為我們執行個體化(instantiate)特定版本的函數,一個版本稱做模板的一個執行個體(instantiation)。當我們調用一個函數模板時,編譯器通常用函數實參來為我們推斷模板實參。當然如果函數沒有模板類型的參數,則我們需要特別指出來:
int a = 10; cout << function(&a) << endl; // 編譯器根據函數實參推斷模板實參 cout << function<int>(&a) << endl; // <int>指出模板參數為int
2、非型別參數
在模板中還可以定義非型別參數(nontype parameter),一個非型別參數表示一個值而非一個類型。我們通過一個特定的類型名而非關鍵字class或typename來指定非型別參數:
// 整形模板 template<unsigned M, unsigned N> void add() { cout<< M+N << endl; } // 指標 template<const char* C> void func1(const char* str) { cout << C << " " << str << endl; } // 引用 template<char (&R)[9]> void func2(const char* str) { cout << R << " " << str << endl; } // 函數指標 template<void (*f)(const char*)> void func3(const char* c) { f(c); } void print(const char* c) { cout << c << endl;} char arr[9] = "template"; // 全域變數,具有靜態生存期 int main() { add<10, 20>(); func1<arr>("pointer"); func2<arr>("reference"); func3<print>("template function pointer"); return 0; }
當執行個體化時,非型別參數被一個使用者提供的或編譯器推斷出的值所替代。一個非型別參數可以是一個整型,或者是一個指向對象或函數的指標或引用:綁定到整形(非型別參數)的實參必須是一個常量運算式,綁定到指標或引用(非型別參數)的實參必須具有靜態生存期(比如全域變數),不能把普通局部變數 或動態對象綁定到指標或引用的非類型形參。
二、類模板
相應的,類模板(class template)是用來產生類的藍圖。與函數模板的不同之處是,編譯器不能為類模板推斷模板參數類型,所以我們必須顯式的提供模板實參。與函數模板一樣,類模板參數可以是型別參數,也可以是非型別參數,這裡就不再贅述了。
template<typename T> class Array { public: Array(T arr[], int s); void print(); private: T *ptr; int size; }; // 類模板外部定義成員函數 template<typename T> Array<T>::Array(T arr[], int s) { ptr = new T[s]; size = s; for(int i=0; i<size; ++i) ptr[i]=arr[i]; } template<typename T> void Array<T>::print() { for(int i=0; i<size; ++i) cout << " " << *(ptr+i); cout << endl; } int main() { char a[5] = {'J','a','m','e','s'}; Array<char> charArr(a, 5); charArr.print(); int b[5] = { 1, 2, 3, 4, 5}; Array<int> intArr(b, 5); intArr.print(); return 0; }
類模板的成員函數
與其他類一樣,我們既可以在類模板內部,也可以在類模板外部定義其成員函數。定義在類模板之外的成員函數必須以關鍵字template開始,後接類模板參數列表。
template <typename T> return_type class_name<T>::member_name(parm-list) { }
預設情況下,對於一個執行個體化了的類模板,其成員函數只有在使用時才被執行個體化。如果一個成員函數沒有被使用,則它不會被執行個體化。
類模板和友元
當一個類包含一個友元聲明時,類與友元各自是否是模板是相互無關的。如果一個類模板包含一個非模板的友元,則友元被授權可以訪問所有模板的執行個體。如果友元自身是模板,類可以授權給所有友元模板的執行個體,也可以只授權給特定執行個體。
// 前置聲明,在將模板的一個特定執行個體聲明為友元時要用到 template<typename T> class Pal; // 普通類 class C { friend class Pal<C>; // 用類C執行個體化的Pal是C的一個友元 template<typename T> friend class Pal2; //Pal2所有執行個體都是C的友元;無須前置聲明 }; // 模板類 template<typename T> class C2 { // C2的每個執行個體將用相同類型執行個體化的Pal聲明為友元,一對一關聯性 friend class Pal<T>; // Pal2的所有執行個體都是C2的每個執行個體的友元,不需要前置聲明 template<typename X> friend class Pal2; // Pal3是普通非模板類,它是C2所有執行個體的友元 friend class Pal3; };
類模板的static成員
類模板可以聲明static成員。類模板的每一個執行個體都有其自己專屬的static成員對象,對於給定的類型X,所有class_name<X>類型的對象共用相同的一份static成員執行個體。
template<typename T> class Foo { public: void print(); //...其他動作 private: static int i; }; template<typename T> void Foo<T>::print() { cout << ++i << endl; } template<typename T> int Foo<T>::i = 10; // 初始化為10 int main() { Foo<int> f1; Foo<int> f2; Foo<float> f3; f1.print(); // 輸出11 f2.print(); // 輸出12 f3.print(); // 輸出11 return 0; }
我們可以通過類類型對象來訪問一個類模板的static對象,也可以使用範圍運算子(::)直接存取靜態成員。類似模板類的其他成員函數,一個static成員函數也只有在使用時才會執行個體化。