1. 程式人生 > >C++ Primer第五版筆記——模板引數與成員模板

C++ Primer第五版筆記——模板引數與成員模板

模板引數
類似函式引數的名字,一個模板引數的名字也沒有什麼內在含義,通常將型別引數命名為T,但是實際上可以是任何名字。
模板引數與作用域:
模板引數遵循普通的作用域規則。一個模板引數的可用範圍是在其聲明後,至模板宣告或定義結束之前。與其他名字一樣,模板引數會隱藏外層作用域中宣告的相同的名字。但是不同的是,在模板內不能重用模板引數名:

typedef double A;
template <typename A,typename B> void f(A a,B b){
    A tmp = a;      //隱藏外部的A,此處tmp不是double型別
    double
B; //錯誤 }

正常的名字隱藏規則使得外部的A的宣告被隱藏,因此tmp不是一個double型別變數,其型別在使用f函式時才會確定,另外由於不能重用模板引數名,所以將變數名取為B是錯誤的。
因為引數名不能重用,因此一個模板引數名在一個特定的函式模板中也只能出現一次:

//錯誤,模板引數名重用
template <typename V,typename V> //...

使用類的型別成員:
通常可以通過::來訪問static成員或者型別成員。在普通的程式碼中,編譯器知道類的定義,因此它可以知道通過作用域運算子訪問到的名字是型別還是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指明返回值是一個型別,並在c中沒有元素的時候生成一個值初始化的元素返回給呼叫者。(看不懂就把T::value_type換成int看看)
當希望通知編譯器一個名字表示型別時,必須使用關鍵字typename而不能使用class。

預設模板實參:
新標準中,可以為函式和類模板提供預設實參,更早的只能為類模板提供預設實參。
例如,使用標準庫中的less函式物件模板編寫compare函式:

template <typename T typename F = less<T>>
int compare(const T& t1,const T& t2,F f = F()){
    if(f(t1,t2)) return -1;
    if(f(t2,t1)) return 1;
    return 0;
}

當例項化時的型別是less支援的型別時,就不用自己提供比較操作:

bool i = compare(0,42);         //預設使用less

當呼叫函式傳入的實參是自定義型別,第三個引數也可自己傳值。

模板預設實參與類模板:

template <typename T = int> class Number{
public:
    Number(T v = 0) : val(v){}
private:
    T val;
};

//例項化
Number<double> d_num;       //不使用預設型別
Number<> i_num;             //使用預設型別

成員模板
一個類(無論是普通類還是類模板)可以包含本身是模板的成員函式。這種成員被稱為成員模板。成員模板不能是虛擬函式。

普通類的成員模板:
作為例子,這裡定義一個類,類似刪除器的作用,類中包含一個過載的函式呼叫運算子,它接受一個指標並對此指標執行delete操作,這裡將呼叫運算子定義為一個模板:

class DebugDelete{
public:
    //建構函式,用於初始化輸出流
    DebugDelete(std::ostream &s = std::cerr):os(s){}
    //T的型別由編譯器推斷
    template <typename T> void operator()(T* p) const{
        os<<"deleteing p"<<endl;
        delete p;
    }
private:
    std::ostream &os;
};

//代替delete操作
double* p = new double;
DebugDelete d;
d(p);               //釋放p

與其他模板相同,成員模板也是以模板引數列表開始的,每個DebugDelete物件都有一個ostream成員,用於寫入資料;還包含一個自身是模板的成員函式,這個類可以代替delete操作符。

類模板的成員模板:
對類模板也可以為其定義成員模板,在此情況下,類和成員各自有各自的、獨立的模板引數。
例如為模板類Blob定義一個建構函式,接受兩個迭代器引數,表示要拷貝的元素的範圍。由於希望支援不同型別序列的迭代器,因此將建構函式定義為模板:

template <typename T> class Blob{
    template <typename It> Blob(It a,It b);
};

建構函式有自己的模板型別引數It,與類模板的普通成員函式不同的是成員模板是函式模板,當在外部定義一個成員模板時,必須同時為類模板和成員模板提供模板引數列表:

template <typename T>       //類的模板型別引數
template <typename It>      //建構函式的類模板型別引數
Blob<T>::Blob(It a,It b){...}

例項化和成員模板:
為了例項化一個類模板的成員模板,須同時提供類和函式模板的實參。和往常一樣,在哪個物件上呼叫成員模板,編譯器就根據該物件的型別推斷類模板引數的實參。與普通函式模板相同,編譯器通常根據傳遞給成員模板的函式實參來推斷它的模板實參。

vector<long> v = {0,1,2,3,4};
//例項化物件和建構函式
Blob<int> s(v.begin(),v.end());

定義s時,顯式的指出了編譯器應該例項化出一個int版本的Blob,建構函式自己的型別引數則通過傳遞的引數來推斷,這裡是vector<long>::iterator,因此會例項化為以下版本:

Blob<int>::Blob(<vector<long>::iterator>,<vector<long>::iterator>);