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、可以爲類模板的模板參數定義一個默認值,該默認值可以引用前一步定義的模板參數。