所谓swap(置换)两对象值,意思是将两对象的值彼此赋予对方。缺省情况下swap动作可由标准程序库提供的swap算法完成。
namespace std {
tempate<typename T>
void swap(T& a, T& b) {
T temp(a);
a = b;
b = temp;
}
}
只要类型T支持copying(通过copy构造函数和copy assignment操作符完成),缺省的swap实现代码就会帮你置换类型为T的对象,你不需要为此另外再做任何工作。
有一种设计类型叫pimpl手法 ,pointer to implementation的缩写,一旦要置换两个对象值,我们唯一需要做的就是置换其pImpl指针,但缺省的swap算法不知道这一点。它不仅复制三个对象,还复制三个指针对象,非常缺乏效率!
我们希望能够告诉std::swap:当该种对象被置换时真正该做的是置换其内部的pImpl指针。确切实践这个思路的一个做法是:std::swap针对对象特化。
class Widget {
public:
...
void swap(Widget& other) {
using std::swap;
swap(pImpl, other.pImpl);
}
};
namespace std {
template<>
void swap<Widget>( Widget& a, Widget& b) {
a.swap(b);
}
}
我们令Widget声明一个名为swap的public成员函数做真正的置换工作,然后将std::swap特化,令它调用该成员函数。
“template<>”表示它是std::swap的一个全特化版本,函数名称之后的表示这一特化版本系针对“T是Widget”而设计;换句话说当一般性的swap template施行于Widgets身上变回启用这个版本。
通常我们不被允许改变std命名空间内的任何东西,但可以为标准templates制造特化版本,使它专属于我们自己的classes。
这样的做法不仅能够通过编译,还与STL容器有一致性,因为所有STL容器也都提供有public swap成员函数和std::swap特化版本。
我们企图偏特化一个function template,但c++只允许对class templates偏特化,在function templates身上偏特化是行不通的。当我们打算偏特化一个function templates时,惯常做法是简单地为它添加一个重载版本,一般而言,重载function templates没有问题,但std是个特殊的命名空间,其管理规则也比较特殊。客户可以全特化std内的templates,但不可以添加新的templates到std里头。
那该如何是好?我们还是声明一个non-member swap让它调用member swap,但不再将那个non-member swap声明为std::swap特化版本或重载版本。为简化起见,假设Widget的所有相关机能都被置于命名空间内,整个结果看起来便像这样:
namespace WidgetStuff {
...
template<typename T>
void swap(Widget<T>& a, Widget<T>& b) {
a.swap(b);
}
}
所以如果你想让你的“class专属版”swap在尽可能多的语境下调用,你需要同时在该class所在命名空间内写一个non-member版本以及一个std::swap特化版本。
C++的名称查找法则确保将找到global作用域或T所在之命名空间之内的任何T专属的swap。如果T是Widget并位于命名空间WidgetStuff内的swap。如果没有T专属的swap存在,编译器就使用std内的swap,这得感谢using声明式让std::swap在函数内曝光,然而即便如此编译器还是比较喜欢std::swap的T专属特化版,而非一般化的那个template,所以如果你已针对T将std::swap特化,特化版会被编译器挑中。
成员版swap决不可抛出异常,那是因为swap的一个最好的应用是帮助classes提供强烈的异常安全性保障。注意这个约束只施行于成员版!