二、類模板( Class Templates)

1 類似於函數模板,類模板也是類型的參數化。

例子,在頭文件中類模板聲明和定義:

#include <vector> 
#include <stdexcept> 

template <typename T> 
class Stack { 
  private: 
    std::vector<T> elems;     // elements 

  public: 
    void push(T const&);      // push element 
    void pop();               // pop element 
    T top() const;            // return top element 
    bool empty() const {      // return whether the stack is empty 
        return elems.empty(); 
    } 
}; 
template <typename T> 
void Stack<T>::push (T const& elem) 
{ 
    elems.push_back(elem);    // append copy of passed elem 
} 

template<typename T> 
void Stack<T>::pop () 
{ 
    if (elems.empty()) { 
        throw std::out_of_range("Stack<>::pop(): empty stack"); 
    } 
    elems.pop_back();         // remove last element 
} 

template <typename T> 
T Stack<T>::top () const 
{ 
    if (elems.empty()) { 
        throw std::out_of_range("Stack<>::top(): empty stack"); 
    } 
    return elems.back();      // return copy of last element 

在上面的例子中,使用的C++標準庫中std::vector<>,因此不需要手動實現內存管理、拷貝構造、賦值操作等函數,只需要實現類模板的接口。

1.1、類的聲明和函數的聲明類型,也是聲明一個類型參數T。

template <typename T> 
class Stack { 
  … 
}; 

在類模板內,T做爲任意的類型可以聲明成員變量也可以聲明成員函數。上面的例子中,聲明瞭一個包含T類型的vector。這個類的類型是Stack,T是模板參數。當聲明變量或函數是,都要寫成Static。
例如,聲明copy構造函數和assignment從操作:

template <typename T> 
class Stack { 
    … 
    Stack (Stack<T> const&);                 // copy constructor 
    Stack<T>& operator= (Stack<T> const&);   // assignment operator 
    … 
};

如果,只需要類型名而不需要類型T,那麼只是有statck就可以了,例如類的構造函數和析構函數。

1.2、成員函數的定義
類模板成員函數的定義,必須指定類模板的成員函數是一個模板函數,因此需要類模板的全程。
如下,類模板Statc的成員函數push的定義:

template <typename T> 
void Stack<T>::push (T const& elem) 
{ 
    elems.push_back(elem);    // append copy of passed elem 
} 

2、類模板的使用

使用類模板時,需要顯示的指定模板參數。
例如:

#include <iostream> 
#include <string> 
#include <cstdlib> 
#include "stack1.hpp" 

int main() 
{ 
    try { 
        Stack<int>         intStack;       // stack of ints 
        Stack<std::string> stringStack;    // stack of strings 

        // manipulate int stack 
        intStack.push(7); 
        std::cout << intStack.top() << std::endl; 

        // manipulate string stack 
        stringStack.push("hello"); 
        std::cout << stringStack.top() << std::endl; 
        stringStack.pop(); 
        stringStack.pop(); 
    } 
    catch (std::exception const& ex) { 
        std::cerr << "Exception: " << ex.what() << std::endl; 
        return EXIT_FAILURE;  // exit program with ERROR status 
    } 
} 

上面例子中聲明瞭一個Stack, 類模板中參數表示符T被int所取代。所以intStack是一個對象,此對象的內部成員vetor容納的元素類型是int,它所調用的任何成員方法都是使用int實例化。

只有那些被調用的成員函數纔會被實例化。對於類模板而已,只有成員函數被使用時纔會實例化,這樣會節約時間和空間。另外一個好處是,如果不支持類模板中的某些操作,只要不去使用這些操作依然可以實例化類模板。
例如,某個類模板中有操作符“operator<”,用來對類中元素排序,如果某個類型不支持“<”操作,只要不調用“<”操作還是可以實例化類模板的。

使用關鍵詞typedef,可以讓類模板的使用更加方便。
如下:

typedef Stack<int> IntStack; 

void foo (IntStack const& s)   // s is stack of ints 
{ 
    IntStack istack[10];       // istack is array of 10 stacks of ints 
    … 
}

使用typedef定義了Stack的別名 IntStack。這兩個是同樣的類型,可以互換使用的。

模板參數可是任意的類型,如下:

Stack<float*>      floatPtrStack;  // stack of float pointers 
Stack<Stack<int> > intStackStack;  // stack of stack of ints 

既可以是float指針,也可以是Stack類型,唯一需要的是這些類型能夠支持調用到的操作。

注:上面的定義中兩個“>”之間必須要有空格,否則編譯器會產生語法錯誤。

Stack<Stack<int>> intStackStack;  // ERROR: >> is not allowed 

3、模板特化(Specializations of Class Templates)

可以爲類模板的某些模板參數進行特化。類模板的特化和函數模板的重載類似,類模板的特化可以針對某些類型優化它的實現,也可以修改類模板對某些類型實例化時出現的錯誤的實例化行爲。如果,對類模板特化就需要特化類模板中的所有成員函數。如果只特化類模板中的某個成員函數,那麼就不能特化整個類模板了。

特化類型模板使用關鍵字“template<>”聲明,並且需要顯示指定需要特化的類型,特化的類型寫在類名之後,如下:

template<> 
class Stack<std::string> { 
  … 
}; 

上面例子中爲特化了一個類型是std:: string類型的Stack類型。

特化的類中的成員函數的定義與普通函數類似,要將類型標識符T替換爲特化的類型,如下:

void Stack<std::string>::push (std::string const& elem) 
{ 
    elems.push_back(elem);    // append copy of passed elem 
} 

4、偏特化(又稱部分特化、局部特化)(Partial Specialization)

類模板可以偏特化。在特殊的情況下,可以對類模板實施部分特化,其餘的類模板參數可以有用戶指定。如下:

//類模板
template <typename T1, typename T2> 
class MyClass { 
  … 
};
//偏特化
// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

// partial specialization: second type is int 
template <typename T> 
class MyClass<T,int> { 
  … 
}; 

// partial specialization: both template parameters are pointer types 
template <typename T1, typename T2> 
class MyClass<T1*,T2*> { 
  … 
};

類模板使用:

MyClass<int,float> mif;    // uses MyClass<T1,T2> 
MyClass<float,float> mff;  // uses MyClass<T,T> 
MyClass<float,int> mfi;    // uses MyClass<T,int> 
MyClass<int*,float*> mp;   // uses MyClass<T1*,T2*> 

如果使用類聲明與多個類模板的偏特化都匹配的化,編譯器因歧義而產生錯誤,如下:

MyClass<int,int> m;        // ERROR: matches MyClass<T,T> 
                           //        and MyClass<T,int> 
MyClass<int*,int*> m;      // ERROR: matches MyClass<T,T> 
                           //        and MyClass<T1*,T2*> 

針對上面例子中在那個的,第二個產生的歧義的,可以再定義一個偏特化的模板,模板參數是兩個同類型的指針,如下:

template <typename T> 
class MyClass<T*,T*> { 
  … 
};

5、默認模板參數

可以爲類模板的模板參數設置默認值,這些默認值被稱爲默認模板參數。默認值甚至可以引用前一個聲明的類模板的默認模板參數。
如下,可以爲類模板Stack<>的第二個參數指定默認值:

#include <vector> 
#include <stdexcept> 

template <typename T, typename CONT = std::vector<T> > 
class Stack { 
  private: 
    CONT elems;               // elements 

  public: 
    void push(T const&);      // push element 
    void pop();               // pop element 
    T top() const;            // return top element 
    bool empty() const {      // return whether the stack is empty 
        return elems.empty(); 
    } 
}; 

template <typename T, typename CONT> 
void Stack<T,CONT>::push (T const& elem) 
{ 
    elems.push_back(elem);    // append copy of passed elem 
} 

注:上述例子中有兩個類模板參數,在定義成員函數的時候,要包含這兩個類模板參數“T”和“CONT”

在使用類模板Stack<>的時候,可以只傳一個類型參數也可以傳遞兩個類型參數。
如下,:

Stack<int> intStack; 
Stack<double,std::deque<double> >

只傳遞一個類型參數,第一類型參數就是int,第二個參數就是默認的vector;

傳遞兩個類型參數,第一類型參數就是double,第二個參數是deque;

6、總結

1、所謂類模板,就是包含一個或多個未確定類型的類。
2、使用類模板時必須將具體的類型作爲參數傳給類模板,編譯器實例化該類型的類模板。
3、類模板中只有被調用的成員函數纔會被實例化。
4、可以針對特定的類型,對類模板進行特化。
5、可以針對特定的類型,對類模板進行偏特化。
6、可以爲類模板的模板參數定義一個默認值,該默認值可以引用前一步定義的模板參數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章