首先说下标准库的swap算法:
1 namespace std{ 2 template<typename T> 3 void swap(T & a, T & b) 4 { 5 T tmp = a; 6 a = b; 7 b = tmp; 8 } 9 }
显然的,标准库实在是索然无味,自己随便写都能写出来,而且这样写有时候对于某些来说效率是特别低的。看看下面这个这个例子:
1 class WidgetImpl{ 2 public: 3 ... 4 private: 5 int a, b, c; 6 std::vector<double> v; 7 ... 8 }; 9 class Widget{ 10 pubcic: 11 Widget(const Widget & rhs); 12 Widget & operator=(const Widget & rhs) 13 { 14 ... 15 *pImpl = *(rhs.pImpl); 16 ... 17 } 18 private: 19 WidgteImpl * pImpl; 20 };
这个operator=配上上面那个swap一起的效率便非常的地下,本来交换Widget中Impl两者的指针就可以很高效的完成swap操作,但是这里硬是活生生的拷贝了两个对象。
那么怎么完成高效的swap函数呢,首先想到的可能就是将标准库中的swap特例化,像下面这样:
1 namespace std{ 2 template<> 3 void swap<Widget>(Widget & a, Widget & b) 4 { 5 swap(a.Impl, b.Impl); 6 } 7 }
上面这个swap是标准库swap的total template specialization版本,但是他并不能通过编译,原因是因为其访问了对象的私有函数。正确的做法一般是下面这样:
1 class Widget{ 2 public: 3 ... 4 void swap(Widget & other){ 5 using std::swap; 6 swap(pUmpl, other.pUmpl); 7 } 8 ... 9 }; 10 namespace std{ 11 template<> 12 void swap<Widget>(Widget & a, Widget & b) 13 { 14 a.swap(b); 15 } 16 }
上面的做法与STL标准库也是高度一致,标准库的容器也都是有std版本的swap而且都在自己内部空间实现了swap供给前者调用。
但是上述的做法在Widget是类模板的时候就不起作用了。因为下面的式子:
namespace std{ template<typename T> void swap<Widget<T> >(Widget<T> & a, Widget<T> & b) { a.swap(b); } }
被c++规定为非法的。
所以所,在无法进行模板特例化的情况下,就需要在特定的自己定义的名字空间创造一个swap函数(或者在std空间中重载一个版本,但是c++是不允许那样做的)。那么据类似下面这样:
1 namespace WidgetStuff{ 2 ... 3 template<typename T> 4 class Widget{.......}; 5 ... 6 template <typename T> 7 void swap(Widget <T> & a, Widget<T> & b) 8 { 9 a.swap(b); 10 } 11 }
这样就大功告成了。。
这样,在任何的位置调用有关Widget的swap的时候,就会首先在WidgetStuff的名字空间里面寻找相应的swap。
注意Widget的成员函数版本的swap中有一行是加上了下划线的,这个using声明语句的作用是即使当这个名字空间中的swap对Impl不能起到作用的时候,至少可以让Impl可以借助于std::swap来完成交换的操作。
总结一下:
如果因为系统默认版本的swap效率不足话,那么就要做下面这几件事:
1. 提供一个class public swap对象,使其高效的置换两个特殊对象的值
2. 在这个class或者template所在的命名空间提供一个non-memberswap,作用是来调用上述的
swap.
3.如果上述的class或者class template实际上是个class而非template,最好是特例化std::swap
,并且用这个来调用member的swap函数。
4.在使用swap的时候,尤其是在自己定义的命名空间下,记得使用一个using 声明吧std::swap包含进来,这样即使在本命名空间中找不到合适的swap,至少也可以调用std的swap.且使用swap的时候不要再前面加上名字空间声明,这样前面声明的std::swap就白做了
注意,成员版本的swap绝对不能抛出异常,但是非成员版本的swap函数是允许抛出异常的。前者不抛出异常的原因是其往往是针对内置类型的操作,而内置类型的操绝对不允许抛出异常。