C++异常处理的三个境界

原创 2011年01月07日 17:41:00
2005年5月份,我转正后1个月,组里组织我们到青岛旅游,那个时候我正在看Exceptional C++这本书,有一个章节一直看不懂,就打印了带到青岛去了,嘿嘿,旅游还是有助于激发灵感的,在旅馆里我终于看懂了,回来以后总结了一个PPT。这个PPT很有特点,因为我做了一个Q版。时光飞逝阿,转眼2年多过了,房价又涨了好多,本来可以买两室两厅的钱只够一室一厅了。特重写C++异常处理献给我们可爱的房地产开发商.
 
Exception-Safety Issues and Techniques
预练武功,先修心法
看看金庸武侠小说中的那些少年俊才,在开始都是不一定有很高的技巧,但总是在年少的时候就或得到高人指点,或得到武林内功秘籍,并且都是心地善良。作为一名C++的入门弟子,同样,在碰到异常这个主题时,有两条要铭记在心:
1. 异常安全不是口号,从一开始就要贯彻执行。
在设计程序时就要考虑的异常的处理,不要给自己借口,说以后碰到了再说吧。千万不能懒惰!等把代码都写的七七八八了,再来添加异常处理。
2. 要有怀疑一切的谨慎态度,要知道,所有的语句都是靠不住的。
如果你拿不出100%的信心说这段代码一定exceptional-safe,那这段代码就是异常不安全的。
总之,首先要培养 异常安全的意识。
两个概念:
Exception-Safe
这里的safe就是说从表现给外部的功能上来说,我总是正常工作了。至于我为了正常工作,饿了肚子,取消了和女朋友的约会之类的事情,外部的调用代码你们不需要关心。
Exception-Neutral:
我只是个传话筒,我虽然是个基层领导,但是我不作为,一线的员工有什么意见,我一字不改的转达给我的上司。下层函数抛上来的异常,我原封不动的throw给上层函数。但是并不是所有的基层领导都是不作为的,他们要把底层员工的意思包装一下,整理一下…然后再汇报给上层,这就叫做non-fully exception-neutral.
new和delete: 你们到底做了什么?!
new和delete的两步曲,我想大家都了解的:分配内存和调用构造函数/调用析构函数和释放内存。本着怀疑一切的态度,如果分配内存失败了呢,如果构造函数失败了呢,如果析构函数失败了呢。就new和delete的层次上,编译器已经帮我们做了很多工作,如果我们希望new和delete是Exception-Safe的,最好打开编译器的/GX开关。
我们来看一段小程序
template <class T>
Stack<T>::Stack()
:v_(0),     //这个Stack底层用数组实现,数组指针初始化为0
vsize_(10), //希望为数组分配可以容纳10个元素的内存
vused_(0)   //现在这个Stack为空
{
 v_ = new T[vsize_]; //initial allocation
}
分析一下这段代码
1.这段代码是exception-neutral的。如果new抛异常,Stack的构造函数只是原封不动的将下层异常throw给上层。
2.这段代码没有内存泄漏。如果内存分配了,但是某个T的构造函数失败,/GX会保证new分配的内存会被释放
3.这段代码的状态是合理的。如果内存分配失败,std::bad_alloc被抛出,Stack的构造函数失败,这是合理的。如果内存分配成功,第N(N<10)个T对象的构造函数失败,/GX会保证前面N-1个已经被成功构造的对象的析构函数被调用,并且释放内存。所以整体状态也是合理的。
好了,正式进入修炼,本心法一共有3层
第一层: 我指保证没有泄漏资源,其他的就难说了。
可以参考两本书:《Coping with Exceptions》 和 《More Effective C++》
怎么能保证我能够到达第一层呢? 
第一层还是挺简单的,只要不泄漏资源(内存)就可以了,一个常用的技巧是把一定要在函数退出时需要释放的资源封装在一个临时对象中,这样可以保证在函数退出时临时对象析构时将资源释放。
为了保证第一层还有一句话是这样讲的:
恶魔!抛出异常的析构函数。为什么这样说呢,并不是说析构函数不能抛出异常,原因是这样的,如果析构函数是由于用户代码造成的,那不要紧。但是有时候析构函数是由/GX以后由编译器添加的代码调用的,看这样的情况:
CMyObject* p = new CMyObject[10];
如果内存被成功分配,在调用第7个CMyObject的构造函数的时候有失败,编译器为了保证行为合理,会依次调用前面成功的6个对象的析构函数然后释放内存,如果在这个时候前面6个析构函数中任何一个失败(有异常抛出),就会调用Terminate终止程序,这是我们不愿意看到的。为了保证这一点,我们常常看到这样的函数声明:
void operator delete[](void*) throw();
void operator delete[](void*,size_t) throw();
 
第二层:我一切都很好,但还是受了内伤
我们看一段代码,秉着每一条语句都是靠不住的意识来分析一下
template<class T>
void Stack<T>::Push(const T& t)
{
     if(vused_ == vsize_) //内存不够,重新申请,等于判断不会抛异常
     {
          size_t vsize_new = vsize_*2 + 1; //将内存扩大一倍,不会抛异常
          T* v_new         = NewCopy(v_,vsize_,vsize_new); //如果抛异常,内存没有分配,该Stack对象的状态不变(Safe)
          delete[] v_;     // this can’t throw(由第一层保证了)
          v_ = v_new;      // 夺取拥有权,赋值操作不抛异常
          vsize_ = vsize_new;  //赋值操作不抛异常
      }
      ++ vused_;         // 自加操作不抛异常
      v_[vused_] = t;   // 如果复制构造失败,Stack状态就不对了!
}
那不简单,我把最后两条语句改一下不就可以了吗?!
v_[vused_+1] =t;
++vused_;
恩,没有错,从功能表现上来说,这样就既满足了exceptional-safe 又满足了exceptionl-neutral。但是这个Stack还是受了内伤,她的内部状态改变了哦!细心一点,我们来看一下:
假设现在这个stack中指向的内存空间的地址是0×1000(v_=0×1000),内存大小为vsize_=10,已经用掉的内存大小vsize_=10,现在,在这个stack上进行一个push操作。好了,内存不够,按照上面代码的逻辑,假设到v_[vused_+1] =t;之前,所有的操作都成功,那现在stack中的状态是这样的:vsize_ =21, vused_=10,v_ =0×2000,现在v_[vused_+1] = t失败,有异常抛出,好了,从外部看,一切都还是合理的。但是在stack内部,vsize_和v_都被改掉了,内存消耗增加了。这就是“内伤”,后面我会谈到一个叫做shrink to fit的技巧来回收多余的内存。
 
第三层:异常她轻轻的来了,又轻轻的走了…
这是异常处理的最高境界,善后工作做的事情好象从来没有发生过一样,这里面一个基本逻辑就是commit-rollback。这里推荐一本书<SGI Exception-Safe Standard Library Adaptation>,作者是 Dave Abrahams.
怎么操作呢?
纵观人类历史,到处都有这样的案例(就算是程序员,也要好好学历史啊-:)):
找个替死鬼(Temp Object),赚了功劳都是我的(Swap),亏了过错都是他的(Destroy by Exception-Unwinding)!
这里有两个技术细节,我们一个一个来欣赏:
1. 精心制作的copy constructor
王道–〉
Stack::Stack(const Stack&other):StackImpl<T>(other.vused_)
{
   while(vused_ < other.vused_)
   {
        construct(v_ + vused_, other.v_[vused_]);
        ++ vused_;
   }
}
2. 偷天换日的swap
template <class T>
void swap( T& a, T& b )
{
     T  temp(a); a = b; b = temp;
}
void StackImpl<T>::Swap( StackImpl & other)  throw()
 {
       swap( v_,     other.v_ );
       swap( vsize_, other.vsize_ );
       swap( vused_, other.vused_ );
 }
 
现在,push的代码改成–>
void Push( const T& t )
 {
      if( vused_ == vsize_ )      // grow if necessary
      {
           Stack temp( vsize_*2+1 );
           while( temp.Count() < vused_ )
           {
                 temp.Push( v_[temp.Count()] );
           }
           temp.Push( t );
           Swap( temp );
      }
      else
      {
           construct( v_+ vused_, t );
           + + vused_;
      }
}
分析一下成功和失败的情况下stack中的状态变化.
 
<writing…>

设计的三个境界:见山三部曲

青山禅师在回顾自己的参禅经历的时候这样说:“三十年前未参禅时,见山是山,见水是水。及至后来,亲见知识,有个入处。见山不是山,见水不是水。而今得个休歇处,依前见山只是山,见水只是水……”这段话是典型的中...
  • xiammy
  • xiammy
  • 2007年01月16日 12:14
  • 2295

C++异常处理编程的三个境界

这是上一次看完Herb Sutter的《Exceptional C++》 后形成的看法,因为懒于更新Blog,一直没有写下来。 一般讲到三个境界,很多人会联想到……#1见山是山,见水是水#2见山不是...
  • u011676589
  • u011676589
  • 2013年08月30日 09:48
  • 534

惊呼——SVM支持向量机三重境界!

转载自:原文 前言     动笔写这个支持向量机(support vector machine)是费了不少劲和困难的,原因很简单,一者这个东西本身就并不好懂,要深入学习和研究下去需...
  • alwaystry
  • alwaystry
  • 2017年03月09日 11:34
  • 748

C语言异常处理编程的三个境界

这是上一次看完Herb Sutter的《Exceptional C++》 后形成的看法,因为懒于更新Blog,一直没有写下来。   一般讲到三个境界,很多人会联想到……#1见山是山,见水是水#2...
  • TreeFish2012
  • TreeFish2012
  • 2013年12月21日 15:03
  • 1197

程序员的八种境界

本文为翻译初稿。更多精彩内容,敬请关注《高效能程序员的修炼》,人民邮电出版社。 在求职的时候,相信很多人都被问过这样的问题,“你对自己未来5年的职业规划是怎么样的?” 每当我被问起这个问题的时候,我的...
  • happydeer
  • happydeer
  • 2012年10月24日 16:09
  • 20322

三个境界

I don't know what I do'not know. I know what I don't know. I already know what I need to know.
  • zhanggang807
  • zhanggang807
  • 2014年11月21日 09:56
  • 439

程序员10大境界

作者简介:周伟明先生毕业于上海交通大学,1994年开始 从事专业软件开发,曾工作于美国加州硅谷的DASCOM Inc公司(现为IBM的全资子公司)和华为技术有限公司等企业。在网络安全软件、服务端软件、...
  • zy563900384
  • zy563900384
  • 2013年09月14日 11:26
  • 2422

C++异常处理机制总结

参考文档:《C++编程思想》《C++Primer》《More effective C++》 一、             传统的错误处理机制: 1.         返回值或全局错误状态标志。缺点:需...
  • MulinB
  • MulinB
  • 2007年08月29日 10:07
  • 2263

做软件测试工作的三重境界

测试的第一重境界:围着Bug转 “意 识决定行动,行动决定结果”是管理学中众所周知的名言。 测试的第一重境界:围着Bug转 “意 识决定行动,行动决定结果”是管理学中众所周知的名言。做测试的前几年...
  • Alan_Wdd
  • Alan_Wdd
  • 2015年04月23日 13:54
  • 780

EM算法的九层境界:​Hinton和Jordan理解的EM算法

原文链接:点击打开链接 摘要: 前言 为什么说EM算法是他们强强发力的领域呢? 这里我们讨论Hinton和统计大神Jordan的强强发力的领域。当Bayes网络发展到高级阶段, 概率图模型使...
  • qq_40954115
  • qq_40954115
  • 2017年12月18日 14:44
  • 199
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++异常处理的三个境界
举报原因:
原因补充:

(最多只允许输入30个字)