swap是个有趣的函数。原本它只是STL的一部分,可是它成为异常安全性编程的脊柱,以及用来处理自我赋值的一个常见机制。可以看到swap函数的重要性。所以我们要讨论一下它的复杂度。
缺省情况下swap是由标准程序库提供的swap算法完成的,如下:
namespace std {
template<typename T> //std::swap的典型实现
void swap(T& a, T&b)
{
T temp(a);
a = b;
b = temp;
}
}
只要类型T支持copying(通过copy构造函数和copy assignment操作符完成),上面的代码就会帮你置换类型为T的对象。
一些时候我们需要为我们自己的类型提供特化的swap函数,因为std中的swap可能对这些类型效率不好。
1) 当我们的类型的数据成员是一个指针,而这个指针指向一个含有真正数据的对象时,使用标准的swap交换这样的对象时,它不仅要复制三个这个类的对象,还要复制三个类中指针所指类的对象,效率很差。而实际上只要交换两个对象中的指针就可以。所以我们为这样的类型提供特化的swap函数。举个例子说明:
class Widget { //我们自己的类型
public:
Widget(const Widget& rhs);
Widget& operator=(cosnt Widget& rhs); //注意复制Widget时,复制的是WidgetImpl对象
private:
WidgetImpl* pImpl; //指针,所指对象内含Widget数据
};
解决方法:将std::swap针对Widget特化。通常我们不能够(不被允许)改变std命名空间内的任何的东西,但可以(被允许)为标准templates(如swap)制造特化版本,使它专属于我们自己的classes。
我们令Widget声明一个名为swap的public成员函数做真正的置换工作,然后将std::swap特化,令它调用该成员函数。
class Widget { //与前相同,就是增加了swap函数
public:
....
void swap(Widget& other)
{
using std::swap;
swap(pImpl, other.pImpl); 若要置换Widgets就置换其pImpl指针
}
....
};
namespace std {
template<> //template<>表示它是std::swap的一个全特化版本
void swap<Widget>(Widget& a, Widget& b) //<Widget>表示这一特化版本针对"T是Widget"而设计的
{ //也就是说一般性的swap template施行于Widgets身上便会使用这个版本
a.swap(b);
}
}
上面的做法还与STL容器在一致性,因为所有STL容器也都提供有public swap成员函数和std::swap特化版本(用以调用前者)
简单的了解了一下上面的代码后,我们整理一下思路:
首先,如果swap的缺省实现的代码对你的class或class templates提供可接受的效率,你不需要额外做任何事。
其次,如果swap的缺省实现版本的效率不足,试着做以下的事情:
1) 提供一个public swap成员函数,让它高效的置换你的类型的两个对象值。
2) 在你的class或class template所在的命名空间提供一个non-member swap,并令它调用上述swap成员函数
3) 如果你正编写一个class而非class template,为你的class特化std::swap。并令它调用你的swap成员函数
总结:
1) 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
2) 如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes而非templates,也请特化std::swap
3) 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何命名空间资格修饰
4) 为用户定义类型进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西