2.1 类模板Stack的实现 Implementation of Class Template Stack
正如函数模板,可以如下方式在一个头文件中声明和定义类Stack<>
:
// basics/stack1.hpp
#include <vector>
#include <cassert>
template <typename T>
class Stack
{
private:
std::vector<T> elems; //元素
public:
void push(T const& elem); //压入元素
void pop(); // 弹出元素
T const& top() const; //返回顶上的元素
bool empty() //返回栈stack是否为空
{
return elems.empty();
}
};
template <typename T>
void Stack<T>::push(T const& elem)
{
elems.push_back(elem); //将elem的拷贝放入elems末尾
}
template <typename T>
void Stack<T>::pop()
{
assert(!elems.empty());
elems.pop_back(); //移除最后一个元素
}
template <typename T>
T const& Stack<T>::top() const
{
assert(!elems.empty());
return elems.back(); //返回最后一个元素
}
正如所看到的,该类模板使用C++标准库的vector<>
实现,这样便无需实现内存管理、拷贝控制和赋值运算,这样便可以将重心放在类模板的接口上。
2.1.1 声明类模板 Declaration of Class Templates
声明类模板与声明函数模板类似:在声明之前,必须声明一个或多个类型参数的标识符。再一次,T是一个常用的标识符:
template <typename T>
class Stack
{
...
};
此处的关键字typename
也可以用class
代替:
template <class T>
class Stack
{
...
};
在类模板中,T可以像其他任何类型一样用于声明成员和成员函数(member function)。该例子中,T用于声明成员的类型为T的向量vector,用于声明成员函数push()
使用T类型作为参数,用于声明成员函数top()
的返回类型。
template <typename T>
class Stack
{
private:
std::vector<T> elems; //元素
public:
void push(T const& elem); //压入元素
void pop(); // 弹出元素
T const& top() const; //返回顶上的元素
bool empty() //返回栈stack是否为空
{
return elems.empty();
}
};
该类的类型为Stack,其中T为模板参数。因此,只要在声明中使用该类的类型,必须使用Stack,除非模板参数可以推断而得。然而,在类模板内,使用没有模板实参的类名意味着将类模板实参作为模板参数(However, inside a class template using the class name not followed by template arguments represents the class with its template parameters as its arguments.)(详见13.2.3节)。
比如,如果必须声明构造函数和赋值运算符,这通常看起来像这样:
template <typename T>
class Stack
{
...
Stack(Stack const&); //拷贝构造
Stack& operator=(Stack const&); //赋值运算符
};
这与如下形式等价:
template <typename T>
class Stack
{
...
Stack(Stack<T> const&); //拷贝赋值
Stack<T>& operator=(Stack<T> const&); //赋值运算符
};
但通常意味着对特殊模板参数的特殊处理,因此第一种形式更好。
然而,在类结构之外需要指定模板参数:
template <typename T>
bool operator==(Stack<T> const& lhs, Stack<T> const& rhs);
注意到在需要类名而不是类的类型的地方,仅仅使用Stack便可以。这特别是在构造函数和析构函数名字的情形中。
与非模板类不同,不能在函数内部或者块作用域(block scope)
内声明类模板。通常,模板只能定义在全局作用域(global scope)或者命名空间作用域(namespace scope)或者类声明内(详见12.1节)。
2.1.2 成员函数实现 Implementation of Member Functions
定义类模板的成员函数必须指定这是一个模板且必须使用类模板的完整类型限制。因此,类型Stack的成员函数push()
的实现为
template <typename T>
void Stack<T>::push(T const& elem)
{
elems.push_back(elem); //将elem的拷贝放入elems末尾
}
该情况下,成员向量的push_bask()
方法被调用,将元素放入向量vector的末尾。
注意到向量vector的pop_back()
将移除最后一个元素但不返回,这行为的原因是异常安全(exception safety)。不可能实现一个返回移除元素的完全异常安全的pop()
版本(该问题首先由Tom Cargill在[CargilExceptionSafety]中的第10项条款[SutterExceptional]中讨论)。然而,如果忽略该危险,可以实现返回移除的元素的pop()
。为实现此功能,简单地使用T来声明类型为元素类型的局部变量:
template <typename T>
T Stack<T>::pop()
{
assert(!elems.empty());
T elem = elems.back(); //保存最后一个元素
elems.pop_back(); //移除最后一个元素
return elem; //返回保存元素的拷贝
}
由于当vector中没有元素时,back()
(返回最后一个元素)和pop_back()
(移除最后一个元素)都将有未定义的行为,因此需要检查栈是否为空。如果为空,则断言(assert),因为在空的栈上调用pop()
是错误的。在top()
上也需要这么做,它返回顶上的元素但不移除:
template <typename T>
T const& Stack<T>::top() const
{
assert(elems.empty());
return elems.back(); //返回最后一个元素
}
当然,对于任何成员函数,也可以将类模板的成员函数在类的声明中实现为inline,比如:
template <typename T>
class Stack
{
...
void push(T const& elem)
{
elems.push_back(elem); //将elem放入末尾
}
};