Effective C++读书笔记(16)

来源:互联网 发布:淘宝良心二手手机店铺 编辑:程序博客网 时间:2024/06/10 19:37

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

swap函数是异常安全性编程的脊柱,以及用来处理自我赋值可能性的一个常见机制。

缺省情况下,swap动作可由标准程序库提供的swap算法完成。(对象需要支持copying函数)

namespace std{template <typename T>void swap(T& a, T& b){      T temp(a);      a = b;      b = temp;}
这种方法效率较低,需要将对象进行三次复制运算,每次运算都复制里面的每个成员变量。改进方法:采用“pimpl方法”(pointer to implementation),定义一个专门封装真正数据的类,然后原有类别包含一个指针指向该数据类。
class WidgetImpl {  //针对Widget数据设计的classpublic:     ....private:     int a,b,c;   //包含Widget的数据     std::vector<double> v;     .....};class Widget {public:   Widget(const Widget& rhs);   Widget& operator=(const Widget& rhs) {        ...        *pImpl = *rhs.pImpl;//进行深度拷贝,将指针内的数据进行复制        ...    }    ...private:    WidgetImpl* pImpl;};

一旦要置换两个Widget对象值,我们唯一需要做的就是置换其pImpl指针,但是缺省的swap算法不知道这一点。它仍然复制pImpl所指对象的所有内容,所以我们应该将std::swap针对Widget特化,但是因为swap函数要访问Widget里面的private成员,因此我们可以将这个特化版本声明为friend,但是这个版本的特化和以往的规则不同:在类中声明一个public成员函数做真正的置换工作,然后将std::swap特化,让其调用该成员函数这种做法不仅可以实现功能,还可以跟STL容器有一致性,因为所有STL容器也都提供有public swap成员函数和std::swap特化版本):

class Widget {public:    ...   void swap(Widget& rhs)   {      using std::swap;  //这个声明的必要性稍后解释      swap(pImpl, rhs.pImpl);   }   ...};namespace std {   template <>   void swap<Widget> (Widget& a, Widget& b)   //特化版本   {      a.swap(b);   }}


然而如果Widget和WidgetImpl都是class template, 而不是 class时,即

template <typename T>class WidgetImpl {....};template <typename T>class Widget {.......};
如果像以往一样进行特化会生成对函数的偏特化

namespace std {   template<typename T>   void swap< Widget<T> >(Widget<T>& a, Widget<T>& b)   { a.swap(b);}}

C++只允许对class templates偏特化,在function templates上偏特化是行不通的。(当打算偏特化一个函数模版时,常用做法是简单的为他添加一个重载模版版本)

namespace std {   template<typename T>   void swap(Widget<T>& a, Widget<T>& b)   { a.swap(b);}}

但是,std是个特殊的命名空间,其管理规则也比较特殊。客户可以全特化std内的templates,但不可以添加新的templates(classes或者functions或其他任何东西)到std里面。因此,我们的做法是我们还是声明一个non-member swap让它调用member swap,但不再将那个non-member swap声明为std::swap的特化版本或者重载版本,而是将其放在另一个命名空间内。

namespace WidgetStuff {   ...   template<typename T>   class Widget {...};   ...   template<typename T>   void swap(Widget<T>& a, Widget<T>& b)   {      a.swap(b);   }}

现在,任何地点的任何代码如果打算置换两个Widget对象,因而调用swap,C++的名称查找法则会找到WidgetStuff内的Widget专属版本。


如果你想让你的“class专属版”swap在尽可能多的语境下被调用,你需要同时在该class所在的命名空间内些一个non-member版本以及一个std::swap特化版本(为了让std::swap特化版本被发现,所以应该如前述代码一样加上using std::swap)。



总结:首先,如果swap的缺省实现码对你的class或者class template提供可接受的效率,你不需要额外做任何事。任何尝试置换(swap)那种对象的人都会去的缺省版本,而那将会有良好的运作。

其次,如果swap缺省实现版的效率不足(那几乎总是意味着你的class或者template使用了某种pimpl手法),试着做以下事情:

1.提供一个public swap成员函数,让你高效地置换你的类型的两个对象值。2.在你的class或者template所在的命名空间内提供一个non-member swap,并令其调用swap成员函数。3.如果正编写一个class(而非class template),为你的class特化std::swap,并令它调用你的swap成员函数。

最后,如果你调用swap,请确定包含一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸的调用swap。


成员版swap绝不可抛出异常。因为swap的一个最好的应用是帮助classes提供强烈的异常安全性保障。这一技术基于一个基础:成员版swap绝不抛出异常。



0 0
原创粉丝点击