收藏 分销(赏)

C++实例-编写异常安全的C++代码方法.docx

上传人:a199****6536 文档编号:6768816 上传时间:2024-12-21 格式:DOCX 页数:11 大小:17.54KB
下载 相关 举报
C++实例-编写异常安全的C++代码方法.docx_第1页
第1页 / 共11页
C++实例-编写异常安全的C++代码方法.docx_第2页
第2页 / 共11页
点击查看更多>>
资源描述
C++实例:编写异常安全的C++代码方法 使用特别还是返回错误码?这是个争辩不休的话题。大家肯定听说过这样的说法:只有在真正特别的时候,才使用特别。那什么是“真正特别的时候”?在答复这个问题以前,让我们先看一看程序设计中的不变式原理。   对象就是属性聚合加方法,如何判定一个对象的属性聚合是不是处于规律上正确的状态呢?这可以通过一系列的断言,最终下一个结论说:这个对象的属性聚合规律上是正确的或者是有问题的。这些断言就是衡量对象属性聚合对错的不变式。   我们通常在函数调用中,实施不变式的检查。不变式分为三类:前条件,后条件和不变式。前条件是指在函数调用之前,必需满意的规律条件,后条件是函数调用后必需满意的规律条件,不变式则是整个函数执行中都必需满意的条件。在我们的争论中,不变式既是前条件又是后条件。前条件是必需满意的,假如不满意,那就是程序规律错误,后条件则不肯定。现在,我们可以用不变式来严格定义特别状况了:满意前条件,但是无法满意后条件,即为特别状况。当且仅当发生特别状况时,才抛出特别。   关于何时抛出特别的答复中,并不排斥返回值报告错误,而且这两者是正交的。然而,从我们阅历上来说,完全可以在这两者中加以选择,这又是为什么呢?事实上,当我们做出这种选择时,必定意味着接口语意的转变,在不转变接口的状况下,其实是无法选择的(试试看,用返回值处理构造函数中的错误)。通过不变式区分出正常和特别状况,还可以更好地提炼接口。   对于特别安全的评定,可分为三个级别:根本保证、强保证和不会失败。   根本保证:确保消失特别时程序(对象)处于未知但有效的状态。所谓有效,即对象的不变式检查全部通过。   强保证:确保操作的事务性,要么胜利,程序处于目标状态,要么不发生转变。   不会失败:对于大多数函数来说,这是很难保证的。对于C++程序,至少析构函数、释放函数和swap函数要确保不会失败,这是编写特别安全代码的根底。   首先从特别状况下资源治理的问题开头.许多人可能都这么干过:   Type* obj = new Type;   try{ do_something...}   catch(...){ delete obj; throw;}   不要这么做!这么做只会使你的代码看上去混乱,而且会降低效率,这也是始终以来特别名声不大好的缘由之一. 请借助于RAII技术来完成这样的工作:   auto_ptr obj_ptr(new Type);   do_something...   这样的代码简洁、安全而且无损于效率。当你不关怀或是无法处理特别时,请不要试图捕获它。并非使用try...catch才能编写特别安全的代码,大局部特别安全的代码都不需要try...catch。我成认,现实世界并非总是如上述的例子那样简洁,但是这个例子的确可以代表许多特别安全代码的做法。在这个例子中,boost::scoped_ptr是auto_ptr一个更适合的替代品。   现在来考虑这样一个构造函数:   Type() : m_a(new TypeA), m_b(new TypeB){}   假设成员变量m_a和m_b是原始的指针类型,并且和Type内的申明挨次全都。这样的代码是担心全的,它存在资源泄漏问题,构造函数的失败回滚机制无法应对这样的问题。假如new TypeB抛出特别,new TypeA返回的资源是得不到释放时机的.曾经,许多人用这样的方法避开特别:   Type() : m_a(NULL), m_b(NULL){   auto_ptr tmp_a(new TypeA);   auto_ptr tmp_b(new TypeB);   m_a = tmp_a.release();   m_b = tmp_b.release();   }   固然,这样的方法的确是能够实现特别安全的代码的,而且其中实现思想将是特别重要的,在如何实现强保证的特别安全代码中会采纳这种思想.然而这种做法不够彻底,至少析构函数还是要手动完成的。我们仍旧可以借助RAII技术,把这件事做得更为彻底:shared_ptr m_a; shared_ptr m_b;这样,我们就可以轻而易举地写出特别安全的代码:   Type() : m_a(new TypeA), m_b(new TypeB){}   假如你觉得shared_ptr的性能不能满意要求,可以编写一个接口类似scoped_ptr的智能指针类,在析构函数中释放资源即可。假如类设计成不行复制的,也可以直接用scoped_ptr。剧烈建议不要把auto_ptr作为数据成员使用,scoped_ptr虽然名字不大好,但是至少很安全而且不会导致混乱。   RAII技术并不仅仅用于上述例子中,全部必需成对消失的操作都可以通过这一技术完成而不必try...catch.下面的代码也是常见的:   a_lock.lock();   try{ ...} catch(...) {a_lock.unlock();throw;}   a_lock.unlock();   可以这样解决,先供应一个成对操作的帮助类:   struct scoped_lock{   explicit scoped_lock(Lock}   ~scoped_lock(){m_l.unlock();}   private:   Lock   };   然后,代码只需这样写:   scoped_lock guard(a_lock);   do_something...   清楚而优雅!连续考察这个例子,假设我们并不需要成对操作, 明显,修改scoped_lock构造函数即可解决问题。然而,往往方法名称和参数也不是那么固定的,怎么办?可以借助这样一个帮助类:   template   struct pair_guard{   pair_guard(FEnd fe, FBegin fb) : m_fe(fe) {if (fb) fb();}   ~pair_guard(){m_fe();}   private:   FEnd m_fe;   ...//制止复制   };   typedef pair_guard , function > simple_pair_guard;  好了,借助boost库,我们可以这样来编写代码了:   simple_pair_guard guard(bind(   do_something...   我成认,这样的代码不如前面的简洁和简单理解,但是它更敏捷,无论函数名称是什么,都可以拿来结对。我们可以加强对bind的运用,结合占位符和 reference_wrapper,就可以处理函数参数、动态绑定变量。全部我们在catch内外的一样工作,交给pair_guard去完成即可。   考察前面的几个例子,或许你已经发觉了,所谓特别安全的代码,竟然就是如何避开try...catch的代码,这和直觉好像是违反的。有些时候,事情就是如此违反直觉。特别是无处不在的,当你不需要关怀特别或者无法处理特别的时候,就应当避开捕获特别。除非你准备捕获全部特别,否则,请务必把未处理的特别再次抛出。try...catch的方式当然能够写出特别安全的代码,但是那样的代码无论是清楚性和效率都是难以忍受的,而这正是许多人抨击C++特别的理由。在C++的世界,就应当根据C++的法则来行事。   假如根据上述的原则行事,能够实现根本保证了吗?恳切地说,根底设施有了,但技巧上还不够,让我们连续分析不够的局部。   对于一个方法常规的执行过程,我们在方法内部可能需要屡次修改对象状态,在方法执行的中途,对象是可能处于非法状态的(非法状态 != 未知状态),假如此时发生特别,对象将变得无效。利用前述的手段,在pair_guard的析构中修复对象是可行的,但缺乏效率,代码将变得简单。的方法是......是避开这么作,这么说有点不厚道,但并非毫无道理。当对象处于非法状态时,意味着此时此刻对象不能安全重入、不能共享。现实一点的做法是:   a.每一次修改对象,都确保对象处于合法状态   b.或者当对象处于非法状态时,全部操作决不会失败。   在接下来的强保证的争论中细述如何做到这两点。   强保证是事务性的,这个事务性和数据库的事务性有区分,也有共通性。实现强保证的原则做法是:在可能失败的过程中计算出对象的目标状态,但是不修改对象,在决不失败的过程中,把对象替换到目标状态。考察一个担心全的字符串赋值方法:   string   locked_pool.deallocate(m_data);   if (rsh.empty())   m_data = NULL;   else{   m_data = locked_pool.allocate(rsh.size() + 1);   never_failed_copy(m_data, rsh.m_data, rsh.size() + 1);   }   }   return *this;   }   locked_pool 是为了锁定内存页。为了争论的简洁起见,我们假设只有locked_pool构造函数和allocate是可能抛出特别的,那么这段代码连根本保证也没有做到。若allocate失败,则m_data取值将是非法的。参考上面的b条目,我们可以这样修改代码:   myalloc locked_pool(m_data);   locked_pool.deallocate(m_data); //进入非法状态   m_data = NULL;//立即再次回到合法状态,且不会失败   if(!rsh.empty()){   m_data = locked_pool.allocate(rsh.size() + 1);   never_failed_memcopy(m_data, rsh.m_data, rsh.size() + 1);   }   现在,假如locked_pool失败,对象不发生转变。假如allocate失败,对象是一个空字符串,这既不是初始状态,也不是我们预期的目标状态,但它是一个合法状态。我们说明了实现根本保证所需要的技巧局部,结合前述的根底设施(RAII的运用),完全可以实现根本保证了...哦,其实还是有一点疏漏,不过,那就留到最终吧。   连续,让上面的代码实现强保证:   myalloc locked_pool(m_data);   char* tmp = NULL;   if(!rsh.empty()){   tmp = locked_pool.allocate(rsh.size() + 1);   never_failed_memcopy(tmp, rsh.m_data, rsh.size() + 1); //先生成目标状态   }   swap(tmp, m_data); //对象安全进入目标状态   m_alloc.deallocate(tmp);//释放原有资源   强保证的代码多使用了一个局部变量tmp,先计算出目标状态放在tmp中,然后在安全进入目标状态,这个过程我们并没有损失什么东西(代码清楚性,性能等等)。看上去,实现强保证并不比根本保证困难多少,一般而言,也的确如此。不过,别太自信,举一种典型的很难实现强保证的例子,对于区间操作的强保证:   for (itr = range.begin(); itr != range.end(); ++itr){   itr->do_something();   }   假如某个do_something失败了,range将处于什么状态?这段代码仍旧做到了根本保证,但不是强保证的,依据实现强保证的根本原则,我们可以这么做:   tmp = range;   for (itr = tmp.begin(); itr != tmp.end(); ++itr){   itr->do_something();   }   swap(tmp, range);   好像很简洁啊!呵呵,这样的做法并非不行取,只是有时候行不通。由于我们额外付出了性能的代价,而且,这个代价可能很大。无论如何,我们阐述了实现强保证的方法,怎么取舍则由您打算了。   接下来争论最终一种特别安全保证:不会失败。   通常,我们并不需要这么强的安全保证,但是我们至少必需保证三类过程不会失败:析构函数,释放类函数,swap。析构和释放函数不会失败,这是RAII技术有效的基石,swap不会失败,是为了“在决不失败的过程中,把对象替换到目标状态”。我们前面的全部争论都是建立在这三类过程不会失败的根底上的,在这里,弥补了上面的那个疏漏。   一般而言,语言内部类型的赋值、取地址等运算是不会发生特别的,上述三类过程规律上也是不会发生特别的。内部运算中,除法运算可能抛出特别。但是地址访问错通常是一种错误,而不是特别,我们本应当在前条件检查中就发觉的这一点的。全部不会发生特别操作的简洁累加,仍旧不会导致特别。   好了,现在我们可以总结一下编写特别安全代码的几条准则了:   1.只在应当使用特别的地方抛出特别   2.假如不知道如何处理特别,请不要捕获(截留)特别。   3.充分使用RAII,旁路特别。   4.努力实现强保证,至少实现根本保证。   5.确保析构函数、释放类函数和swap不会失败。   另外,还有一些语言细节问题,由于和这个主题有关也一并列出:   1.不要这样抛出特别:throw new exception;这将导致内存泄漏。   2.自定义类型,应当捕获特别的引用类型:catch(exception& e)或catch(const exception& e)。   3.不要使用特别标准,即使是空特别标准。编译器并不保证只抛出特别标准允许的特别,更多内容请参考相关书籍。
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 包罗万象 > 大杂烩

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服