C++ 函式模板與類模板
16.1.1 函式模板
tmplate
+模板引數列表
template<typename T>
int compare(const T &v1, const T &v2){
if(v1<v2) return -1;
if(v2<v1) return 1;
return 0;
}
模板引數列表不能為空
例項化:實現模板的某個特定版本
cout<<compare(0,1)<<endl; //T為int
cout<<compare(vector1, vector2); //T為vector<int>
非型別模板引數:表示一個值而非一個型別
值必須是常量表達式,模板在編譯時被例項化
- 如果是整型:必須是常量表達式
- 如果是引用或指標:傳入的實參必須有靜態生存期static
例如,我們可以編寫一個compare版本處理字串字面常量。這種字面常量是constchar 的陣列。由於不能拷貝一個數組,所以我們將自己的引數定義為陣列的引用(參見6.2.4節,第195頁)。由於我們希望能比較不同長度的字串字面常量,因此為模板定義了兩個非型別的引數。第一個模板引數表示第一個陣列的長度,第二個引數表示第二個陣列的長度:
template<unsigned N, unsigned M>
int compatre(const char (&p1)[N], const char (&p2)[M]){
if(N<M) return -1;
if(M<N) return 1;
return 0;
}
compare("hi", "mom");
//編譯器例項化出:
int compare(const char (&p1)[3], const char (&p2)[4]);
模板的編譯
當編譯器遇到一個模板定義時,它並不生成程式碼。
只有當我們例項化
模板則不同:為了生成一個例項化版本,編譯器需要掌握函式模板或類模板成員函式的定義。因此,與非模板程式碼不同,模板的標頭檔案通常既包括宣告也包括定義。
最佳實踐:定義一個函式模板print,遍歷列印任何型別任何大小的陣列
template<typename T, unsigned N>
void print(T (&array)[N]){
for(T it : array){
cout<<it<<" ";
}
cout<<endl;
}
16.1.2 類模板
與函式模板不同,類模板在例項化時要提供額外型別資訊
定義類模板
template<typename T> class Blob{
public:
typedef T vale_type;
typedef typename vector<T>::size_type size_type;
//建構函式
Blob();
Blob(std::initializer_list<T> il);
//Blob中的元素數目
size_type siez() const {return data->size(); }
bool empty() const {return data->empty(); }
//新增和刪除元素
void push_back(const T &t) {data->push_back(t);}
void piop_back();
//元素訪問
T& back();
T& operator[](size_type i);
private:
shared_ptr<vector<T>> data;
//若data[i]無效,則丟擲異常
void check(size_type i, const string &msg) const;
};
例項化模板
Blob<int> ia; //空Bolb<int>
Blob<int> ia2 = {0,1,2,3,4}; //有5個空元素的Blob<int>
當編譯器從我們的Blob模板例項化出一個類時,它會重寫Blob模板,將模板引數T的每個例項替換為給定的模板實參,在本例中是int。
在類外定義成員函式
在類外定義成員函式時,不僅要表明類的作用域,還要帶上模板實參
template<typename T>
void Blob<T>::check(size_type i, const string &msg)const{
if( i >= data->size() )
throw std::out_of_range(msg);
}
類模板成員函式的例項化
預設情況下,一個類模板的成員函式只有當程式用到它時才進行例項化。例如,下面程式碼
//例項化Blob<int>和接受initializer_list<int>的建構函式
Blob<int> squares = {0, 1,2,3,4,5,6,7,8,9};
//例項化Blob<int>::size() const
for (size_t i= 0; i != squares.size(); ++i)
squares[i] = i*i;//例項化Blob<int>::operator[](size_t)
例項化了 Blob
如果一個成員函式沒有被使用,則它不會被例項化。成員函式只有在被用到時才進行例項化,這一特性使得即使某種型別不能完全符合模板操作的要求(參見9.2節,第294頁),我們仍然能用該型別例項化類。
預設情況下,對於一個例項化了的類模板,其成員只有在使用時才被例項化
類模板和友元
- 一對一友好關係:兩模板類的同類型例項化互為友元
//前置宣告,在Blob中宣告友元所需要的
template <typename> class BlobPtr;
template <typename> class Blob; //運算子==中的引數所需要的
template <typenaem T>
bool operator==(const Blob<T> &, const Blob<T>&);
template <typename T> class Blob{
//每個Blob例項將訪問許可權授予用相同型別例項化的BlobPtr和相等運算子
friend class BlobPtr<T>;
friend bool operator==<T>
(const Blob<T>&, const Blob<T>&);
//其他成員定義不變
};
友元的宣告用Blob的模板形參作為它們自己的模板實參。因此,友好關係被限定在用相同型別例項化的Blob 與 BlobPtr相等運算子之間;
Blob<char> ca; //BlobPtr<char>和operator==<char>都是本物件的友元
Blob<int> ia; //BlobPtr<int>和operator==<int>都是本物件的友元
- 通用和特定的模板友好關係:一個模板類將另一個模板類的型別的例項都宣告為自己的友元
//前置宣告,在模板類宣告時需要用到
template <typename T> class Pal;
class C{//C是一個普通的非模板類
friend class Pal<C>; //用類C例項化的Pal是C的一個友元
//Pal2的所有例項化都是C的友元,這種情況無需前置宣告
template <typename T> friend class Pal2;
};
template <typename T> class C2{//C2本身是一個類模板
//C2的每個例項都將相同的例項化的Pal宣告為友元
friend class Pal<T>; //Pal模板什麼必須在作用域之內
//Pal2的所有例項都是C2的所有例項的友元,不需要前置宣告
template <typename X> friend class Pal2;
//Pal3是一個非模板類,它是C2所有例項的友元
friend class Pal3; //不需要Pal3的前置宣告
};
為了讓另一模板類的所有例項成為友元,友元宣告中必須使用與類模板本身不同的模板引數。
- 令模板自己型別成為友元
8template <typename Type> class Bar{
friend Type; //將訪問許可權授予用來例項化的Bar型別
//...
};
模板類型別名
由於模板不是一種型別,所以我們不能用typedef
而要用using
template<typename T> using twin = pair<T,T>;
twin<string> authors; //authors是一個pair<string, string>
一個模板型別是一族類的別名
twin<int> win_loss; //win_loss是一個pair<int,int>
twin<double> area; //area是一個pair<double,double>
當我們使用模板類型別名時,可以固定一個或多個引數
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; //books是一個pair<string, unsigned>
partNo<Vehicle> cars; //cars是一個pair<Vehicle, unsigned>
partNo<Student> kisd; //kids是一個pair<Student, unsigned>
類模板引數的static成員
每種型別的例項化物件間共享,不同型別的例項化物件間不共享
16.1.3 模板引數
使用類的型別成員
回憶一下,我們用作用域運算子(::)來訪問static成員和型別成員。在普通(非模板)程式碼中,編譯器掌握類的定義。因此,它知道通過作用域運算子訪問的名字是型別還是 static成員。例如,如果我們寫下string::size_type
,編譯器有string 的定義,從而知道size_type是一個型別。
但對於模板程式碼就存在困難。例如,假定T是一個模板型別引數,當編譯器遇到類似T::mem
這樣的程式碼時,它不會知道mem是一個型別成員還是一個static資料成員,直至例項化時才會知道。
但是,為了處理模板,編譯器必須知道名字是否表示一個型別。例如,假定T是一個型別引數的名字,當編譯器遇到如下形式的語句時:
T::size_type * p;
它需要知道我們是正在定義一個名為p的變數還是將一個名為size_type的static資料成員與名為p的變數相乘。
預設情況下,C++語言假定通過作用域運算子訪問的名字不是型別。因此,如果我們希望使用一個模板型別引數的型別成員,就必須顯式告訴編譯器該名字是一個型別。我們通過使用關鍵字typename
來實現這一點:
template <typename T>
typename T::value_type top(const T& c)
{
if ( !c.empty())
return c.back();
else
return typename T::value_type ();
}
當我們希望通知編譯器一個名字表示型別時,必須使用關鍵字typename,而不能使用class。
預設模板實參
函式模板的預設實參
就像我們能為函式引數提供預設實參一樣,我們也可以提供預設模板實參(default template argument)。在新標準中,我們可以為函式和類模板提供預設實參。而更早的C++標準只允許為類模板提供預設實參。
例如,我們重寫compare,預設使用標準庫的less函式物件模板
/// l compare有一個預設模板實參less<T>和一個預設函式實參F()
template <typename T, typename F = less<T>>
int compare (const T &v1, const T &v2,F f = F()){
if ( f(v1, v2) ) return -1;
if (f(v2, v1) ) return 1;
return 0;
}
在這段程式碼中,我們為模板添加了第二個型別引數,名為F,表示可呼叫物件的型別;並定義了一個新的函式引數f,繫結到一個可呼叫物件上。
我們為此模板引數提供了預設實參,併為其對應的函式引數也提供了預設實參。預設模板實參指出compare將使用標準庫的 less函式物件類,它是使用與 compare一樣的型別引數例項化的。預設函式實參指出f將是型別F的一個預設初始化的物件。
當用戶呼叫這個版本的compare時,可以提供自己的比較操作,但這並不是必需的:
bool i = compare(0,42);//使用less; i為-1
//結果依賴於item1和item2中的isbn
Sales_data item1(cin), item2(cin) ;
bool j = compare (item1,item2,compareIsbn) ;
第一個呼叫使用預設函式實參,即,型別less
在第二個呼叫中,我們傳遞給compare三個實參: compareIsbn(參見11.2.2節)和兩個sales_data型別的物件。當傳遞給 compare三個實參時,第三個實參的型別必須是一個可呼叫物件,該可呼叫物件的返回型別必須能轉換為bool值,且接受的實參型別必須與compare的前兩個實參的型別相容。與往常一樣,模板引數的型別從它們對應的函式實參推斷而來。在此呼叫中,T的型別被推斷為sales_data,F被推斷為compareIsbn的型別。
與函式預設實參一樣,對於一個模板引數,只有當它右側的所有引數都有預設實參時,它才可以有預設實參。
類模板的預設實參
無論何時使用一個類模板,我們都必須在模板名之後接上尖括號。尖括號指出類必須從一個模板例項化而來。特別是,如果一個類模板為其所有模板引數都提供了預設實參,且我們希望使用這些預設實參,就必須在模板名之後跟一個空尖括號對:
template <class T = int> class Numbers { // T預設為int
public:
Numbers(T v = 0): val(v) { }
//對數值的各種操作
private:
Tval;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; //空<>表示我們希望使用預設型別
此例中我們例項化了兩個 Numbers版本: average_precision是用int 代替T例項化得到的; lots_of _precision是用long double代替T例項化而得到的。