template<typename T>
void swap(T& a, T& b)
{
T temp(a); // 拷贝构造
a = b; // 拷贝赋值运算符
b = temp; // 拷贝赋值运算符
}
一个基本的swap函数大概如上所述,但是对某些类型而言。典型比如:pimpl手法(pointer to implementation),这些复制没有必要。
class WidgetImpl
{
public:
...
private:
int a, b, c;
std::vector<double> v; // 意味着拷贝开销很大
};
class Widget
{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs)
{
...
*pImpl = *(rhs.pImpl);
}
private:
WidgetImpl* pImpl;
}
一旦需要置换两个Widget对象值,我们只需要交换pImpl指针,但缺省的swap算法不知道这一点,它不止拷贝三个Widget,还拷贝三个WidgetImpl对象。
我们希望能够告诉std::swap,当Widgets被置换时真正该做的是置换其内部的pImpl指针,确切的做法就是:将std::swap针对Widget特化。
namespace std {
template<>
void swap<Widget>(Widget& a, Widget& b)
{
swap(a.pImpl, b.pImpl);
}
}
通常我们不被允许改变std命名空间内的任何东西,但可以为标准的template制造特化版本。
虽然上面的代码看起来达到我们的目的,但实际上这个函数无法通过编译,因为它企图访问 a 和 b 内的Impl指针,而那是private的。
但我们可以在内部实现一个swap的public成员函数做真正的置换工作,然后将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);
}
}
这种做法不仅能通过编译,还与STL容器有一致性,因为所有的STL容器也都提供有public swap成员函数和std::swap特化版本。
using std::swap
不抛异常
成员版swap最好实现成不要抛异常,因为swap的一个最好应用是帮助class提供异常安全保障。此技术基于一个假设:成员版的swap绝不抛异常。但这一约束只约束于成员版,不可施行于非成员版,因为swap缺省版本是以拷贝构造和拷贝赋值操作符为基础,而一般情况下两者都允许抛异常。
总结
- 如果
swap的缺省实现对你的class或class template提供可接受的效率。那么不需要做额外的操作。
如果swap的缺省实现版本效率不足,试着做下面的事情:- 提供一个
public swap成员函数,让它高效的置换你的类型的两个对象值,并且这个swap函数绝不能抛出异常。 - 在你的
class或template所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数。 - 如果你在编写一个
class而非class template,为你的class特化std::swap。
- 提供一个