1. 程式人生 > 其它 >C++ 函式模板與類模板

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>

非型別模板引數:表示一個值而非一個型別

值必須是常量表達式,模板在編譯時被例項化

  1. 如果是整型:必須是常量表達式
  2. 如果是引用或指標:傳入的實參必須有靜態生存期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類和它的三個成員函式:operator[]、size和接受initializer_list的建構函式。
如果一個成員函式沒有被使用,則它不會被例項化。成員函式只有在被用到時才進行例項化,這一特性使得即使某種型別不能完全符合模板操作的要求(參見9.2節,第294頁),我們仍然能用該型別例項化類。

預設情況下,對於一個例項化了的類模板,其成員只有在使用時才被例項化

類模板和友元

  1. 一對一友好關係:兩模板類的同類型例項化互為友元
//前置宣告,在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>都是本物件的友元
  1. 通用和特定的模板友好關係:一個模板類將另一個模板類的型別的例項都宣告為自己的友元
//前置宣告,在模板類宣告時需要用到
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的前置宣告
};

為了讓另一模板類的所有例項成為友元,友元宣告中必須使用與類模板本身不同的模板引數。

  1. 令模板自己型別成為友元
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的一個預設初始化物件。在此呼叫中,T為int,因此可呼叫物件的型別為less。compare 的這個例項化版本將使用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例項化而得到的。