类模板
与函数相似,类也可以被一种或多种类型参数化。容器类就是一个具有这种特性的典型例子,它通常被用于管理某种特定类型的元素。只要使用类模板,你就可以实现容器类,而不需要确定容器中元素的类型。在这一章中,我们使用Stack作为类模板的例子。
3.1 类模板Stack的实现
与函数模板的处理方式一样,我们在同一个头文件中声明和定义类Stack< >(我们将在6.3小节讨论如何把声明和定义放在不同的文件中),如下:
//basics/stack1.hpp
#include <vector>
#include <stdexcept>
template <typename T>
class Stack {
private:
std::vector<T> elems; // 存储元素的容器
public:
void push(T const&); // 压入元素
void pop(); // 弹出元素
T top() const; // 返回栈顶元素
bool empty() const { // 返回栈是否为空
return elems.empty();
}
};
template <typename T>
void Stack<T>::push (T const& elem)
{
elems.push_back(elem); // 把elem的拷贝附加到末尾
}
template<typename T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back(); //删除最后一个元素
}
template <typename T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems.back(); // 返回最后一个元素的拷贝
}
可以看出,类模板Stack<>是通过C++标准库的类模板vector< >来实现的;因此,我们不需要亲自实现内存管理、拷贝构造函数和赋值运算符;从而可以把精力放在该类模板的接口实现上。
3.1.1 类模板的声明
类模板的声明和函数模板的声明很相似:在声明之前,我们先(用一条语句)声明作为类型参数的标识符;我们继续使用T作为该标识符:
template <typename T>
class Stack {
...
};
在此,我们可以再次使用关键字class来代替typename:
template <class T>
class Stack {
...
};
在类模板的内部,T可以像其他任何类型一样,用于声明成员变量和成员函数。在下面的例子中,T被用于声明vector的元素类型,声明push()是一个接收常量T引用为唯一实参的成员函数,声明top()是返回类型为T的成员函数:
template <typename T>
class Stack {
private:
std::vector<T> elems; //存储元素的容器
public:
Stack(); //构造函数
void push(T const &); //压入元素
void pop(); //弹出元素
T top() const; //返回栈顶元素
};
这个类的类型是Stack<T>,其中T是模板参数。因此,当在声明中需要使用该类的类型时,你必须使用Stack<T>。例如,如果你要声明自己实现的拷贝构造函数和赋值运算符,那么应该这样编写[8]:
template <typename T>
class Stack {
...
Stack (Stack<T> const&); //拷贝构造函数
Stack<T>& operator= (Stack<T> const&); //赋值运算符
...
};
然而,当使用类名而不是类的类型时,就应该只用Stack;譬如,当你指定类的名称、类的构造函数、析构函数时,就应该使用Stack。
3.1.2 成员函数的实现
为了定义类模板的成员函数,你必须指定该成员函数是一个函数模板,而且你还需要使用这个类模板的完整类型限定符。因此,类型Stack<T>的成员函数push()的实现如下:
template <typename T>
void Stack<T>::push (T const& elem)
{
elems.push_back (elem); //把传入实参elem的拷贝 //附加到末端
}
在上面的例子中,调用了对应vector的push_back()方法,它把传入元素附加到该vector的末端。
请注意:vector的pop_back()方法只是删除末尾的元素,并没有返回该元素;之所以如此是充分考虑了异常安全性,因为要实现“一个绝对异常安全并且返回被删除元素的pop()”是不可能的(Tom Cargill在[CargillExceptionSafety]中首次讨论了这个话题,Sutter在[SutterExceptional]的Item 10也提到这个问题)。然而,如果不考虑异常安全性,我们就可以实现一个返回被删除元素的pop()。事实上,只需要使用T声明一个局部变量,并保证该变量的类型就是vector元素的类型即可;具体如下:
template<typename T>
T Stack<T>::pop()
{
if (elems.empty() ) {
throw std::out_of_range(“Stack<>::pop(): empty Stack”);
}
T elem = elems.back(); //先保存末端元素的拷贝
elems.pop_back(); //删除末端元素
return elem; //返回上面保存的元素的拷贝
}
因为当vector为空的时候,它的back()方法(返回末端元素的值)和pop_back()方法(删除末端元素)会具有未加定义的行为,因此我们需要先检查该stack是否为空。如果为空,就抛出std::out_of_range异常。同样,在top()的实现中,我们也是用这种办法来判断对应stack是否为空;top()只是返回栈的顶端[10]元素,并不删除该元素:
template<typename T>
T Stack<T>::top() const
{
if (elems.empty()) {
throw std::out_of_range(“Stack::top(); empty Stack”);
}
return elems.back(); //返回末端元素的拷贝
}
显然,对于类模板的任何成员函数,你都可以把它实现为内联函数,将它定义于类声明里面。例如:
template <typename T>
class Stack {
...
void push (T const& elem) {
elems.push_back(elem); //把传入的elem实参附加到末端
}
...
};







