Effective C++读书笔记(16)

本文探讨了如何通过特化和成员函数提升swap函数的性能,确保其在自我赋值及异常安全方面发挥重要作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

条款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数据设计的class
public:
     ....
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绝不抛出异常。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值