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

来源:互联网 发布:淘宝关闭蚂蚁花呗支付 编辑:程序博客网 时间:2024/04/29 19:12
swap原本只是STL的一部分,后面成为异常安全编程的脊柱,及处理自我赋值安全性的一个常见机制。例子:标准程序库提供的swap算法
namespace s td{ template<typenameT>void swap(T& a,T& b)      //std::swap的典型实现{T tmp(a);a=b;b=tmp;}}
要求:类型T支持copying(通过copying构造函数和copyassignment操作符完成)
上述代码主要涉及三个对象的复制,但对于某些类型而言,复制并不重要。
 
如pimpl手法(point to implementation)”以指针指向一个对象,内含真正数据”
class WidgetImpl   //针对widget设计的类{         public:                   ...         private:       //数据多,复制时间长                   inta,b,c;         std::vector<double>v;         ...}; class Widget      //采用pimpl手法{         public:                   Widget(constWidget& rhs);         Widget&operator=(const Widget& rhs)  //复制Widget时 令其复制WidgetImpl对象         {                   ...                   *Impl=*(rhs.Impl);                   ...         }                   private:                            WidgetImpl*Impl;  //指针,内含Widget数据};

转换述对象,只需置换pImpl指针,相对比复制对象效率更高。
为了让std::swap知道置换Widget时是置换指针pImpl,可行的方法是将std::swap针对Widg特化,如下构想。
namespace std         {         template<>         voidswap<Widget>(Widget& a,Widget& b) //std::swap针对“T是Widget”的特化版本                   {                            swap(a.Impl,b.Impl);     //很明显不能编译通过,因为没有访问private的权限                   }         }

“template<>”表明它是std::swap的一个全特化版本(total template specialization),函数名后的<Widget>表示这一特化版本是针对“TWidget”而设计。(一般性的swap施行于Widget便会启用这个版本)
通常不允许改变std命名空间内的任何东西,但允许为标准的templates(如swap)制造特化版本,使它专属于某个类(如Widget) 上述不编译不能过,我们可以置其为友员。还有解决方法是:令Widget声明一个名为swap的public成员函数做真正的置换工作。然后将std::swap特化,令它调用该成员函数,如下:
class Widget     //同前,唯一差别是增加了swap{         ...         voidswap(Widget& other)         {                   uingstd::swap;      //如果没有专属swap,就使用std内的swap,(这就是函数内声明std::swap的原因)                   swap(Impl,other.Impl);  //只需交换指针         }         ...};namespace std         {         template<>                   voidswap(Widget& a,Widget& b)    //修订后的std::swap特化版本                   {                            a.swap(b);      //置换Widget,调用其成员函数                   }         }


这种作法能通过编译,还与STL有一至性:因为所有STL容器提供有public swap成员函数和std::swap特化版本。
 
当是类模版而非类时,如下代码:
template<typename T>                   classWidgetImpl{...}; template<typename T>         classwidget{...};namespace std         {         template<typenameT>                   voidswap<Widget<T>>(Widget<T>& a,Widget<T>& b)      //错误,不合法                   {                   a.swap(b);                   }         }


看似合理,实则错误。因为上面我们企图偏特化(partially specialize)一个函数模板(function template(std::swap)),C++仅允许对类模板偏特化
 
当偏化一个函数模版时,惯常做法是简单的函数重载,如下代码,但是也是不合法的、
namespace std    //std::swap的一个重载版本         {         template<typenameT>             //swap后面不跟“< >”                   voidswap (Widget<T>& a,Widget<T>& b)      //错误,不合法                   {                   a.swap(b);                   }         }


一般重载函数模版没问题,但std是特殊的命名空间。标准委员会禁止膨胀那些已经声明好的东西。也就是说:我们可以全特化std内的template,但不能添加任何新的东西(模版或class或functions等)。
 
解决方法:声明一个non-member的swap调用member swap,但不再将non-member声明为std::swap的特化或重载版本。假设所有widger机能位于名字空间WidgetStuff内,如下代码:
    
     namespaceWidgetStuff         {                   ...          //WidgerImpl类同前                   classWidget{...};  //类含swap成员函数                   template<typenameT>                            voidswap<Widget<T>>(Widget<T>& a,Widget<T>& b) //非成员函数,但不属于std空间                   {                            a.swap(b);                   }         }


现在任何地点置换两个widget对象,C++会根据名称查找法则(name lookuprule)找到WidgetStuff内专属版本。
如果想让“class专属版”swap在更多情况下适用,应在该class所在命名空间内写一个non-menber版本和std::swap特化版本
对于default swap,member swap, non-member swaps,std::swap特化版本做个总结:如果默认swap效率好,则使用缺省swap,反之,则、1、提供一个public成员函数,高效转换对象值。(但这个函数绝不能抛出异常2、如果提供一个member swap,也应该提供一个non-memberswap来调用前者。对class(而非类模版),特化std::swap。3、调用swap时应针对std::swap使用using 声明式,然后调用swap不带任何“命名空间资格修饰符”。4、对“用户定义类型”进行stdtemplate全特化。但不能在std内加入全新东西。
0 0
原创粉丝点击