昨晚上无意间看到两篇关于异常的文章,这才再次让我想起来把这个知识点搞清楚,这篇文章全文如下:
http://www.360doc.com/content/09/1014/18/59579_7272623.shtml
以前从来不在代码中使用C++的异常机制,一是觉得对效率影响太大,二是自己对异常安全性实在是一窍不通,想用也不敢用。
看完《exceptional C++》关于异常部分的讨论,才突然有一种豁然开朗的感觉,原来,写出异常安全的代码不是一定要写那些让我浑身不爽的try{...}catch(...){...}代码的 。
首先,需要弄清楚两个基本概念:异常安全性保证和异常中立性。异常安全性保证就是说你的代码在发生异常时能提供什么样的保证,它又分为三个等级: 基本安全保证、强烈安全保证和无异常抛出保证。基本安全保证指异常发生时你的代码不会发生资源泄露,这样能保证最基本的安全性;强烈安全保证指你的代码满 足“提交或回退语义”,即要么完全执行成功提交,要么发生异常回退到代码执行前的状态;无异常抛出保证不是指你在代码中处理掉所有抛出的异常,使其不向外 传播,而是指你的代码在任何情况下都不可能引发任何异常,这是最强的异常安全保证。而异常中立性是指当你的代码(包括你调用的代码)引发异常时,这个异常 能保持原样传递到外层调用代码。一段代码要具有异常安全性,必须同时具有异常中立性和某个级别的异常安全性保证(这个级别也就是你代码异常安全性的程度 了)。
对一些简单的代码当然是希望其满足无异常抛出保证了,如果不行,就要尽量满足强异常安全保证,这里有一些原则和技巧,总的来说,就是1、异常安全 性是在设计时就必须考虑的,它将直接影响设计决策;2、确认所有可能引发异常的代码都安全执行了再执行那些会改变当前状态的代码;3、优先满足内聚性,尽 量让你的函数只完成一件必要工作。所以说,写下 fuc(a++,b); 这样的代码绝对是不行的,要写成 fuc(a,b); ++a; 。
所以我们现在可以明白为什么STL里面很多容器pop方法仅仅弹出元素而不返回,为什么每个标准容器都提供swap方法,为什么STL里面有时候 会看到重载operator=参数是传值而不是传引用,为什么auto_ptr的赋值语义是所有权转换而不是深拷贝,答案只有一个,就是为了满足异常安全 性。
也可以看到,为了满足异常安全性,往往会损失一些效率,所以vector至今仍然只满足基本异常安全保证,我们的代码必须在某些时候作出一个选 择,作出这个选择时,你的头脑必须是清醒的,你必须知道为什么这么做,有什么好处,有什么坏处,呵呵,要完全达到这个要求还需要长期的修炼
http://www.360doc.com/content/09/1014/17/59579_7270514.shtml
一般讲到三个境界,很多人会联想到……#1见山是山,见水是水#2见山不是山,见水不是水#3见山还是山,见水还是水。嗯没错,区区这里说的也是这东西,只不过是有关编程,有关C++,有关异常而已。
事情源起于今天下班时间过后,老大随着他的手机铃声《上海滩》潇洒地下班了。留下区区和梁兄在办公室里,当时他好像在对一个RDI程序进行逆向工程,而区区只是在摆弄oberon。
“你觉得用C++进行异常安全性编程时最重要的范式是什么?try,catch,finally要怎么使用才得当?”梁忽然地就问了。
老实说,牛X得不得了的梁很少跟在下请教问题的(虽然这个问句有强烈的讨论意味,但是区区就权当是在被“请教”了),所以觉得应该尽可能答得好一些。
“编写异常安全的C++程序,最好就是,不要使用try,catch,finally。”——区区这样回答的。
“哦,很怪喔,那怎么能怎么处理呢?”————就知道会被追问~~
于是,那区区就说:
是这样的,有关异常的C++编程,有三个境界:
#第一个境界就是:程序中看到不try,catch,finally。
这是新手的水平,他不知道有的模块/函数是会有异常抛出的,不处理的话,程序会当掉,很多资源会不能及时正确回收。或者他写程序时反复应用errno或者检查返回值的方式来处理异常情况,排错代码和正常流程代码搅在一起,混乱不堪。
#第二个境界就是:程序中看到好多好多try,catch,finally。
这 是入门级的水平,他懂得利用抛异常的方式来处理错误情况,所以在程序中,正常的流程会统一在try里,各种错误处理,都安排在catch当中,小心翼翼地 做好的善后工作。有时候狠起来还使用catch(...)来强行把所有的异常都压下来。这样没有什么混乱?才怪,各种善后处理虽然都做了,但是他不知道要 写多少个try,多少个catch,而且经常要把思路放到catch当中去。
#第三个境界就是:程序中还是看到不try,catch,finally。
然而有不同,这一回他是手中无剑心有剑的高手境界了。
他知道异常安全的三个保证,并且懂得在什么时候分别提供#资源回收保证#数据一致性保证#无异常保证。
---他会使用C++超强的RAII(资源获取即初始化)来使得资源在产生异常时会自动回收(写一个类就可以管理一种资源,一劳永逸,不用天天catch来catch去)。
---他会使用pimpl技法(我比较喜欢叫它疙瘩技法)帮助实现RAII,并把逻辑操作分派到各个成员内部当中,使之在发生异常时保持一致性。
---他另外还会常常使用一个no throw的swap操作一次性把所有的操作完成。这样的话,对象就不会成为烂尾楼。
于是,这个高手写的类自己不用异常来打扰你,如果真的在内部其它类发生了异常,这个异常也安全地透过这个高手的类传到更上一层去,不破坏类本身的数据完整性。虽然异常还会有,但是,安全了~
所以综合来说,要写好一个异常安全的模块,最好有几个东东要牢记于心~::
异常(它会出现),RAII,pimpl,数据一致性,swap。
如果忘了,可以翻出boost::shared_ptr的源程序好好看一遍。(这句话其实没敢跟师兄说)
其实少用try catch,是对于一个相对的说法。当知道一个模块要通过抛异常来报告错误,并且这个错误的处理责任是模块调用者时,就应当使用try,别把异常给漏了。比如说boost::lexical_cast的用户~
再比如梁的模块一般最终要以c函数接口形式发布,那在模块的最上层,加一个try,也是合适的。
而且梁自己也说了,现在正在逆向的这个程序,使用了很好的SEH(这是windows的异常处理功能),所以很容易把握开发者的意图————你看,用异常处理来写程序就是好,连反汇编的可读性都比返回错误码的范式要强得多(当然,有的人不希望被逆向的)。
可以说只是一个引子,然后我又找到一篇文章,一口气读完了,好过瘾,有木有,再加上上篇关于RAII的文章,让我对异常算是有了个大致的认识了吧,虽然不常用,但还是要知道吧,附上链接:
C++程序设计 第十五章 异常_百度文库