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、可以为类模板的模板参数定义一个默认值,该默认值可以引用前一步定义的模板参数。