目錄
-
- 定義一個通用模板
- 模板特化和偏特化
- 模板實例化與匹配
- 可變參數模板
泛型編程是指獨立與任何類型的方式編寫代碼。泛型編程和面向對象編程,都依賴與某種形式的多態。面向對象編程的多態性在運行時應用於存在繼承關系的類,一段代碼可以可以忽略基類和派生類之間的差異。在泛型編程中,編寫的代碼可以用作多種類型的對象。面向對象編程所依賴的多態性稱為運行時多態性,泛型編程所依賴的多態性稱為編譯時多態性或參數式多態性。
1 模板定義
1.1 函數模板
- 模板定義以關鍵字 template 開始,后接模板形參表,模板形參表是用尖括號括住的一個或多個模板形參的列表,形參之間以逗號分隔。模板形參表不能為空。
- 模板函數的類型形參跟在關鍵字 class 或 typename 之后定義.在函數模板形參表中,關鍵字 typename 和 class 具有相同含義,可以互換使用,兩個關鍵字都可以在同一模板形參表中使用
- 函數模板可以用與非模板函數一樣的方式聲明為 inline。說明符放在模板形參表之后、返回類型之前,不能放在關鍵字 template 之前
- 函數模板調用方式。在發生函數模板的調用時,不顯示給出模板參數而經過參數推演,稱之為函數模板的隱式模板實參調用(隱式調用)在發生函數模板的調用時,顯示給出模板參數而不需要經過參數推演,稱之為函數模板的顯示模板實參調用(顯示調用)。顯示模板實參調用在參數推演不成功的情況下是有必要的。
- 函數模板與函數重載。函數模板實際上是建立一個通用函數,其函數類型和形參類型不具體指定,用一個虛擬的類型來代表,凡是函數體相同的函數都可以用這個模板來代替,不必定以多個函數。重載函數的參數個數、參數類型或參數順序3者中必須至少有一種不同,函數返回值類型可以相同也可以不同,函數體可以相同。
1 template<typename T> 2 inline bool isEqual(const T& t1, const T& t2) { 3 return t1 == t2; 4 }
1.2 類模板
- 類模板也是模板,因此必須以關鍵字 template 開頭,后接模板形參表
- 除了模板形參表外,類模板的定義看起來與任意其他類問相似。類模板可以定義數據成員、函數成員和類型成員,也可以使用訪問標號控制對成員的訪問,還可以定義構造函數和析構函數等等。
- 與調用函數模板形成對比,使用類模板時,必須為模板形參顯式指定實參,類模板的形參不存在實參推演的。
1 const size_t MAXSIZE = 100; 2 template<class T> 3 class Stack{ 4 private: 5 T elements[MAXSIZE]; 6 public: 7 //others 8 };
1.3 模板參數
- 類型模板形參:類型形參由關見字class或typename后接說明符構成,如template<class T> void getMaxVal(const T& a,const T& b){};其中T就是一個類型形參,類型形參的名字由用戶自已確定。
- 非類型模板形參:模板的非類型形參也就是內置類型形參,如template<class T, int X> greaterThanX(const T& a);其中int X就是非類型的模板形參。非類型形參在模板定義的內部是常量值,也就是說非類型形參在模板的內部是常量。非類型的模板參數是有限制的,一般是一個整型,它們可以是常整數(包括枚舉類型)或者指向外部鏈接對象的指針。浮點數和類對象是不允許作為非類型模板參數的。
- 模板的默認參數。可以為類模板的類型形參提供默認值,但不能為函數模板的類型形參提供默認值。函數模板和類模板都可以為模板的非類型形參提供默認參數。類模板類型形參默認值和函數的默認參數一樣,如果有多個類型形參則從第一個形參設定了默認值之后的所有模板形參都要設定默認值。類模板的類型形參默認值形式為:template<class T1, class T2=int> class A{};為第二個模板類型形參T2提供int型的默認值,在類模板的外部定義類中的成員時template 后的形參表應省略默認的形參類型。比如template<class T1, class T2=int> class A{public: void h();}; 定義方法為template<class T1,class T2> void A<T1,T2>::h(){}
1 template<typename T,int X = 5> 2 inline bool isEqualToX(const T& a) { 3 return a == X; 4 } 5 6 template<class T,int MAXSIZE=100> 7 class Stack { 8 private: 9 T elements[MAXSIZE]; 10 public: 11 //others 12 };
2.模板特化與偏特化
有時為了需要,針對特定的類型,需要對模板進行特化,也就是特殊處理。 例如,stack類模板針對bool類型,因為實際上bool類型只需要一個二進制位,就可以對其進行存儲,使用一個字或者一個字節都是浪費存儲空間的.。特化必須在同一命名空間下進行,可以特化類模板也可以特化函數模板,但類模板可以偏特化和全特化,而函數模板只能全特化。模板的偏特化是指需要根據模板的某些但不是全部的參數進行特化。嚴格的來說,函數模板並不支持偏特化,但由於可以對函數進行重載,所以可以達到類似於類模板偏特化的效果。模板實例化時會優先匹配”模板參數”最相符的那個特化版本。template < >告訴編譯器這是一個特化的模板。
1 template<class T,int MAXSIZE=100> 2 class Stack { 3 private: 4 T elements[MAXSIZE]; 5 public: 6 //others 7 }; 8 9 //template specializations aim at bool 10 template<> 11 class Stack<bool>{ 12 13 };
1 template<typename T> 2 inline bool isEqual(const T t1, const T t2) { 3 return t1 == t2; 4 } 5 6 //針對int型的指針做特化 7 template<> 8 inline bool isEqual(const int* p1,const int* p2){ 9 return *p1 == *p2; 10 }
類模板的偏特化,例如c++標准庫中的類vector的定義,這個偏特化的例子中,一個參數被綁定到bool類型,而另一個參數仍未綁定需要由用戶指定。
1 template <class T, class Allocator> 2 class vector { // … // }; 3 template <class Allocator> 4 class vector<bool, Allocator> { //…//};
函數模板的偏特化,嚴格的來說,函數模板並不支持偏特化,但由於可以對函數進行重載,所以可以達到類似於類模板偏特化的效果。根據重載規則,對(a)進行重載。如果將(a)稱為基模板,那么(b)稱為對基模板(a)的重載,而非對(a)的偏特化。C++的標准委員會仍在對下一個版本中是否允許函數模板的偏特化進行討論。
template <class T> void f(T); (a)
template < class T> void f(T*); (b)
3 模板實例化與匹配規則
3.1 隱式實例化。在使用模板函數和模板類時,不存在指定類型的模板函數和模板類的實體時,由編譯器根據指定類型參數隱式生成模板函數或者模板類的實體稱之為模板的隱式實例化。函數模板隱式實例化指的是在發生函數調用的時候,如果沒有發現相匹配的函數存在,編譯器就會尋找同名函數模板,如果可以成功進行參數類型推演,就對函數模板進行實例化。類模板隱式實例化指的是在使用模板類時才將模板實例化。
3.2 顯示實例化。顯示實例化也稱為外部實例化。在不發生函數調用的時候將函數模板實例化,或者在不適用類模板的時候將類模板實例化稱之為模板顯示實例化。對於函數模板而言,不管是否發生函數調用,都可以通過顯示實例化聲明將函數模板實例化,定義函數模板為:template函數返回類型 函數模板名<實際類型列表>(函數參數列表),顯示實例化為template void func<int>(const int&);類模板的顯示實例化,對於類模板而言,不管是否生成一個模板類的對象,都可以直接通過顯示實例化聲明將類模板實例化,定義類模板格式為:template class 類模板名<實際類型列表>,顯示實例化為template class theclass<int>;
3.3 匹配規則
(1) 類模板的匹配規則。最優化的優於次特化的,即模板參數最精確匹配的具有最高的優先權,每個類型都可以用作普通型(a)的參數,但只有指針類型才能用作(b)的參數,而只有void*才能作為(c)的參數。
template <class T> class vector{//…//}; // (a) 普通型
template <class T> class vector<T*>{//…//}; // (b) 對指針類型特化
template <> class vector <void*>{//…//}; // (c) 對void*進行特化
(2) 函數模板的匹配規則。非模板函數具有最高的優先權。如果不存在匹配的非模板函數的話,那么最匹配的和最特化的函數具有高優先權
template <class T> void f(T); // (d)
template <class T> void f(int, T, double); // (e)
template <class T> void f(T*); // (f)
template <> void f<int> (int) ; // (g)
void f(double); // (h)
bool b;
int i;
double d;
f(b); // 以 T = bool 調用 (d)
f(i,42,d) // 以 T = int 調用(e)
f(&i) ; // 以 T = int* 調用(f)
f(d); // 調用(g)
4.可變參數模板
參考:http://www.cnblogs.com/qicosmos/p/4325949.html
可變參數模板是C++11新增的特性之一,它對參數高度泛化,他能表示0到任意個數、任意類型的參數。可變模板參數之前會帶有省略號,把帶省略號的參數稱為“參數包”,它里面包含了0到N(N>=0)個模版參數。我們無法直接獲取參數包args中的每個參數的,只能通過展開參數包的方式來獲取參數包中的每個參數,這是使用可變模版參數的一個主要特點。可變模版參數和普通的模版參數語義是一致的,所以可以應用於函數和類,即可變模版參數函數和可變模版參數類,然而,模版函數不支持偏特化,所以可變模版參數函數和可變模版參數類展開可變模版參數的方法還不盡相同。
4.1 可變模板參數函數與參數的展開
- 遞歸函數方式展開參數包。通過遞歸函數展開參數包,需要提供一個參數包展開的函數和一個遞歸終止函數,遞歸終止函數正是用來終止遞歸的。
- 逗號方式展開參數包
1 #include <iostream> 2 using namespace std; 3 //遞歸終止函數 4 void print() 5 { 6 cout << "empty" << endl; 7 } 8 //展開函數 9 template <class T, class ...Args> 10 void print(T head, Args... rest) 11 { 12 cout << "parameter " << head << endl; 13 print(rest...); 14 } 15 16 17 int main(void) 18 { 19 print(1,2,3,4); 20 return 0; 21 }
1 template <class T> 2 void printarg(T t) 3 { 4 cout << t << endl; 5 } 6 7 template <class ...Args> 8 void expand(Args... args) 9 { 10 int arr[] = {(printarg(args), 0)...}; 11 } 12 13 expand(1,2,3,4);
4.2 可變模板參數類與參數展開
可變參數模板類的參數包展開的方式和可變參數模板函數的展開方式不同,可變參數模板類的參數包展開需要通過模板特化和繼承方式去展開,展開方式比可變參數模板函數要復雜。可變參數模板類是一個帶可變模板參數的模板類,比如C++11中的元祖std::tuple就是一個可變模板類,它的定義如下,這個可變參數模板類可以攜帶任意類型任意個數的模板參數。
1 template< class... Types > 2 class tuple; 3 std::tuple<int> tp1 = std::make_tuple(1); 4 std::tuple<int, double> tp2 = std::make_tuple(1, 2.5); 5 std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, “”); 6 std::tuple<> tp;//可變參數模板的模板參數個數可以為0個,所以下面的定義也是也是合法的:
參考