读书笔记:Effective C++ 炒冷饭 - Item 25 关于swap函数的实现

读书笔记:Effective C++ 炒冷饭 - Item 25 关于swap函数的实现
[原创文章欢迎转载,但请保留作者信息]
Justin 于 2009-12-28

笔记写到一半,有一些同学说还是不要放在首页了他们想读自己会去找的。想想也有道理,假借大师的名义污染大众的视线确实还是有点不厚道。
所以后面的笔记会放回新手区。噢~再见了首页~

言归正传,记录大师的第25堂课。
不知道std::swap函数是不是有那么重要(原文说它是异常安全性编程 exception-safe programming的脊柱……),Scott专门用一个Item来说明实现它需要考虑的各种因素。读下来我倒是觉得文中提到的方法或技巧可以用到更多的地方,swap只不过是个典型罢了。

首先大师先是给出最经典当然也是最简单的swap函数实现:用了一个中间临时对象,然后两两交换。这也是std::swap的缺省实现,如果这样的swap已经可以满足需要,那么就不需要再费心考虑,直接用就是了。我们就提前下课。

如果还在读,说明可能意识到:缺省的方法很简单,而在一些情况下却也很耗资源:比如需要交换的是一个很复杂庞大的对象时,创建/拷贝大量数据会使得这种swap的效率显得非常低下。

于是,大师给出更加适应的实现思路:

  1. 在类/模板类(class/class template)中定义一个公有的swap成员函数,这个函数负责实现真正的交换操作。同时,这个函数不能抛出异常。
    用成员是因为交换操作中可能会需要访问/交换类的私有成员;用公有(public)来限制是为了给下面的辅助函数(wrapper function)提供接口。
    至于不能抛出异常,有两个原因:
    • 一是Item29中所提到的异常安全性(exception-safety)需要不抛出异常的swap来提供保障(更多细节就到拜读29条的时候再记录吧。)
    • 二是一般而言高效的swap函数几乎都是对内置类型的交换,而对内置类型的操作是不会抛出异常的。
  2. 如果需要使用swap的是一个类(而不是模板类),就需要为这个类全特化std::swap,然后在这个特化版本中调用第一步中实现的swap函数。
    class  AClass{
    public :
       
    void  swap(AClass &  theOtherA)
       {
          
    using  std::swap;  // 这一句会在稍后的第3点提到
          
    //  通过调用swap来完成该类的特有交换动作
       }
    // ..
    }

    namespace  std{
       
    // 在std名字域内定义一个全特化的swap
       template <>   // 这样的定义说明是全特化
        void  swap < AClass >  ( AClass &  a, AClass &  b)
       {
          a.swap(b);
       }
    }
    如此一来,用户可以直接应用同样的swap函数进行交换操作,而当交换对象是需要特殊对待的AClass对象时,也可以无差别的使用并得到预期的交换结果。
  3. “2.”中说的是当交换“类”时可以采取的办法,而如果我们需要交换的是模板类,那么就不能用全特化std::swap的方法了。然而用偏特化的std::swap也行不通,因为:
    • C++中不允许对函数进行偏特化(只能对类偏特化),也 就是说不能写出下面的程序:
      namespace  std{   
         
      //  illegal code as C++ doesn't allow partial specialization for function templates
         template < typename T >
         
      void  swap <  AClassTemplate < T >   > (AClassTemplate < T >&  a, AClassTemplate < T >&  b)
         {
            a.swap(b);
         }
      }
    • std名字空间中的内容都是C++标准委员会的老大们定义的,为了保证std内部代码的正常运作(同时为了向老大表示尊敬……),委员会以外的小辈们是不允许往里头添加任何新的模板、类、方程或是其他的什么东东的,重载也不可以。因此,虽然可以像2.那样写出全特化的模板函数,但是企图在std的名字空间添加以下重载的swap(这种重载变相实现了函数的偏特化)是不被C++委员会同意的(虽然你可以通过编译,但是会埋下隐患):
      namespace  std{
         template 
      < typename T >
         
      void  swap (AClass < T >&  a, AClass < T >&  b)
         { a.swap(b);}
      }
      给自己的一个小提醒:因为函数名swap后没有<>,所以不是偏特化,而是对
      namespace  std{
         template
      < class  _Ty >  inline
         
      void  swap(_Ty &  _X, _Ty &  _Y)
         {
      /* ..*/}
      }
      的重载而已。

    基于上面两个原因,一个变通的方法是在该模板类所在的名字空间中编写一个非成员的函数模板来调用这个公有的接口:
    namespace  AClassSpace{
       template 
    < typename T >
       
    void  swap (AClass < T >&  a, AClass < T >&  b)
       { a.swap(b);}
    }
    在限定的名字空间中实现函数,是为了避免“污染”全局的名字空间。而且,不同的名字空间都可以使用一样的函数名字而不会有冲突。
    基于前面第23课所学,使用非成员函数也是应该的了。
    至于为什么要函数模板,那就匪常D简单:因为要交换的是模板@#¥%
本课超时,理解上也许还有偏差。不过,Scott老师额外讲了pimpl手法作为友情赠送:
pimpl也即pointer to implementation,第31课会继续讲解。不过说来也简单:当需要交换两个复杂且臃肿的对象时,可以先用两个指针分别指向着两个对象,之后对这些对象的操作,包括交换,就只需要通过这两个指针来进行(交换两个指针的值便实现了对象的交换)。


参考文章:
Why Not Specialize Function Templates?
Template Specialization and Partial Template Specialization

你可能感兴趣的:(读书笔记:Effective C++ 炒冷饭 - Item 25 关于swap函数的实现)