一、问题引入
在前面博客提到的stacks模板类,通常只有当两个 stacks 类型相同,也就是当两个stacks 拥有相同类型的元素时,你才能对它们相互赋值(assign),也就是将某个 stack 整体赋值给另一个。你不能把某种类型的 stack赋值给另一种类型的 stack,即使这两种类型之间可以隐式转型:
Stack<int> intStack1, intStack2; // stacks for ints
Stack<float> floatStack; // stack for floats
...
intStack1 = intStack2; // OK:两个 stacks 拥有相同类型
floatStack = intStack1; // ERROR:两个 stacks 类型不同
default assignment 运算符要求左右两边拥有相同类型,而以上情况中,拥有不同类型元素的两 个 stacks,其类型并不相同。
二、解决思路
然而,只要把 assignment 运算符定义为一个 template,你就可以让两个「类型不同,但其元素 可隐式转型」的 stacks 互相赋值。
为完成此事,Stack<> 需要这样的声明:
template <typename T>
class Stack {
private:
std::deque<T> elems; // 元素
public:
void push(T const&); // push 元素
void pop(); // pop 元素
T top() const; // 传回 stack 顶端元素
// stack 是否为空
bool empty() const {
return elems.empty();
}
// 以「元素类型为 T2」的 stack 做为赋值运算的右手端。
template <typename T2>
Stack<T>& operator= (Stack<T2> const&);
};
新增加的 assignment 运算符实作如下:
template <typename T>
template <typename T2>
Stack<T>& Stack<T>::operator= (Stack<T2> const& op2) {
// 判断是否赋值给自己
if ((void*)this == (void*)&op2) {
return *this;
}
Stack<T2> tmp(op2); // 建立 op2 的一份拷贝
elems.clear(); // 移除所有现有元素
// 复制所有元素
while (!tmp.empty()) {
elems.push_front(tmp.top());
tmp.pop();
}
return *this;
}
三、使用
有了这个 member template,你就可以把一个 int stack 赋值(assign)给一个 float stack:
Stack<int> intStack1, intStack2; //stack for ints
Stack<float> floatStack; //stack for floats
...
floatStack = intStack1; // OK:两个 stacks 类型不同,但 int 可转型为 float。
当然,这个赋值动作并不会改动 stack和其元素的类型。赋值完成后,floatStack 的元素类型还是 float,而 top()仍然传回 float 值。
也许你会认为,这么做会使得类型检查失效,因为你甚至可以把任意类型的元素赋值给另一个 stack。然而事实并非如此。必要的类型检查会在「来源端 stack」的元素(拷贝)被安插到「目 的端 stack」时进行:
elems.push_front(tmp.top());
如 果 你将一个 string stack 赋值 给一个 float stack ,以 上 述 句 编译时就会发生
错 误: 「elems.push_front()无法接受 tmp.top()的返回类型」。具体的错误讯息因编译器而异,但 含义类似。
Stack<std::string> stringStack; // stack of strings
Stack<float> floatStack; // stack of floats
…
floatStack = stringStack; // 错误:std::string 无法转型为 float
注意,前述的 template assignment 运算符并不取代 default assignment 运算符。如果你在相同 类型的 stack 之间赋值,编译器还是会采用 default assignment 运算符。
四、拓展
和先前一样,你可以把内部容器的类型也参数化:
template <typename T, typename CONT = std::deque<T> >
class Stack {
private:
CONT elems; // 元素
public:
void push(T const&); // push 元素
void pop(); // pop 元素
T top() const; // 传回 stack 的顶端元素
// stack 是否为空
bool empty() const {
return elems.empty();
}
// 以元素类型为 T2 的 stack 赋值
template <typename T2, typename CONT2>
Stack<T,CONT>& operator= (Stack<T2,CONT2> const&);
};
此时的 template assignment 运算符可实作如下:
template <typename T, typename CONT>
template <typename T2, typename CONT2>
Stack<T,CONT>& Stack<T,CONT>::operator= (Stack<T2,CONT2> const& op2) {
// 判断是否赋值给自己
if ((void*)this == (void*)&op2) {
return *this;
}
Stack<T2,CONT2> tmp(op2); // 创建 op2 的一份拷贝
elems.clear(); // 移除所有现有元素
//复制所有元素
while (!tmp.empty()) {
elems.push_front(tmp.top());
tmp.pop();
}
return *this;
}
记住,对 class templates 而言,只有「实际被调用的成员函数」才会被实例化。因此如果你不至 于令不同 (元素) 类型的 stacks 彼此赋值, 那么甚至可以拿 vector 当作内部元素的容器 (注: 而先前的程序代码完全不必改动):
// stack for ints,使用 vector 作为内部容器
Stack<int,std::vector<int> > vStack;
...
vStack.push(42);
vStack.push(7);
std::cout << vStack.top() << std::endl;
由于 template assignment 运算符并未被用到,编译器不会产生任何错误讯息抱怨说「内部容器 无法支持 push_front()操作」。