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
。
- 提供一个