关于swap的几点发散思维(3)

    PIMPL手法来保证异常安全

异常,在C++中既是一个好东西,又不是一个好东西。

因为它会打破程序的正常流程,虽然通过异常处理机制可以在一定程度上解决问题,但仍然很容易造成资源泄漏。

同时,用好了它,也会给你带来很多好处。

优秀的程序中,你不用再看到大块的trycatch。已经不需要了。

使用智能指针,RAII,以对象管理资源,可以保证对象析构时候资源的正常释放。没必要通过catch来捕获这个异常再释放。

 

如上所言其实是一种异常安全保证。

异常安全保证有三种级别,分别是BasicStrongNo-throw

上述的要看如何处理这个异常。如果仅仅是释放资源,但这样可能会增大结果的不确定性,这属于Basic保证级别。

如果能够正确处理,使结果能够有效恢复到出错前的状态,这种属于Strong保证级别。

 

这里着重要说的是No-throw,即不抛出任何异常的保证

一般的做法是在函数头的最后加上throw()表示什么也不抛出。

它这样写并不意味着“绝对不会抛出任何异常”,而是意味着一旦抛出,就会调用unexpected指针所指的函数,默认就是terminate

因此,作为程序员的我们,有义务去遵守函数异常规格(no-throw)的约定。

 

呃……先看看以前有人提出的如下问题:

class Widget

{

      // ...

    private:

      T1 t1_;

      T2 t2_;

};

 

Assume that any T1 or T2 operation might throw. Without changing the structure of the class, is it possible to write a strongly exception-safe Widget::operator=( const Widget& )?

 

翻译:

假定T1或者T2的操作可能会抛出异常。那么,在不改变类结构的情况下,是否有可能写出一个重载的=运算符,使它具有强异常安全性?

 

这个问题初看觉得不好解决。

因为我们按照常规思维去写operator=( const Widget& ) ……就是要让自己的成员t1_t2_分别等于参数对象的t1_t2_。可是这样因为T1或者T2的赋值不安全,必然会影响到Widget的赋值不安全。显然不是具有异常安全性的代码!

 

呃……其实呢,我们可以换角度去解决这个问题。

PIMPL的手法就应运而生。

PIMPL的全称是Pointer to Implementation。意思为这个类持有一个“指针”,它指向真正的成员数据。

这样,我们需要做的就是赋值操作符交换两个类的指针

真正的内容被封装在另外一个地方。这样比喻吧——

我们可以认为真正的内容在保险箱中,但是保险箱里面是“危险品”,对它操作就意味着危险(抛出异常)。那么,赋值,有个比较好的方案就是让两个保险箱的持有者交换下钥匙

呃……等等,有个问题——

赋值(ab)的意思是让a的内容跟b一样,并不是交换ab的内容。

没关系,交换是个常用手法。我们先拷贝一份b到一个临时对象c,然后交换ac。这样就没问题了。

 

基于此,我们可以改进上述的Widget为如下的代码:

【以下代码参考More Exceptional C++

class Widget

  {

  public:

    Widget();  //初始化嵌套的WidgetImpl子对象,并让pimpl指向它

~Widget();  //必要的析构

void Swap(Widget& other );

Widget& operator =(const Widget& other);

    // ...

  private:

    class WidgetImpl;  //只是一个保险箱,内部的东西并不一定是this所指对象的。

    auto_ptr<WidgetImpl> pimpl;  //这个是钥匙,所指“保险箱”里才是真正内容。

  };

 

  class Widget::WidgetImpl

  {

  public:

    // ...

    T1 t1_;

    T2 t2_;

  };

 

这里,我们使用了智能指针auto_ptr和嵌套类

……

那么,我们需要交换的就是auto_ptr指针,它是具有绝对异常安全性的(即no-throw保证)。

可以得到如下的swap

 void Widget::Swap( Widget& other ) throw()

  {

    auto_ptr<WidgetImpl>  temp( pimpl);

    pimpl = other.pimpl;

    other.pimpl = temp;

  }

 

它的每一步都是绝对安全的。

赋值操作符的重载代码如下:

 Widget& Widget::operator=( const Widget& other )

  {

    Widget temp( other );

    Swap( temp );

    return *this;

  }

 

这样,我们就解决了文章最初提出的问题。

 

呃……swap函数,其实是std命名空间的一个标准函数。

我们既然这里实现了Widget类的特有Swap,按照《Effective C++》书中作者的思想,我们应该将它添加到标准std namespaceswap的一个完全特化(不是重载)。

 

因为这样做,对于使用这些代码的人,只要知道swap,就可以使用,而无需考虑这个swap的参数到底是否是一般特化的类型。

就算是不小心写出了std::swap的代码,也不会影响程序结果。具体实现如下:

namespace std{

   template<>

void  swap<Widget>(Widget& a,  Widget& b){

   a.Swap(b);   //注意上面的Swap首字母大写哦!

}

}

 

关于PIMPL手法的更多细节,我个人十分推荐Herb Sutter的《Exceptional C++一书。

虽然Scott Meyers 的《Effective C++》也有不少内容是讲解“异常安全”的,不过详细程度比不过《Exceptional C++》。

 

谢谢大家的关注!

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值