条款25:考虑写出一个不抛出异常的swap函数

首先说下标准库的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函数是允许抛出异常的。前者不抛出异常的原因是其往往是针对内置类型的操作,而内置类型的操绝对不允许抛出异常。
 
posted @ 2015-10-09 09:46  eversliver  阅读(375)  评论(0编辑  收藏  举报