[转]第14集 再探C++中异常的rethrow

使用boost::rethrow_exception重新抛出异常的测试程序 然后,在外部的catch块中,我们使用boost::current_exception()函数获取当前异常,并将其传递给boost::rethrow_exception函数。通过使用boost::rethrow_exception,我们可以在异常处理程序中灵活地重新抛出异常,从而实现更复杂的异常处理逻辑。boost::rethrow_exception是Boost库中的一个函数,它允许开发者在异常处理程序中重新抛出当前异常。最终,我们会捕获并处理重新抛出的异常,并输出异常的错误信息。 阅读详情

 From: http://se.csai.cn/ExpertEyes/200801031114531905.htm

 

第14集 再探C++中异常的rethrow

http://www.csai.cn 作者: 王胜祥 来源:希赛网 2008年1月3日   

  在相遇篇中的《第5集 C++的异常rethrow》文章中,已经比较详细讨论了异常重新被抛出的处理过程。但是有一点却并没有叙述到,那就是C++异常重新被抛出时(rethrow),异常对象的构造、传递和析构销毁的过程会有哪些变化和不同之处。为了精益求精,力求对每一个细节都深入了解和掌握,下面再全面阐述一下各种不同组合情况下的异常构造和析构的过程。

  大家现在知道,异常的重新被抛出有两种方式。其一,由于当前的catch block块处理不了这个异常,所以这个异常对象再次原封不动地被重新抛出;其二,就是在当前的catch block块处理异常时,又激发了另外一个异常的抛出。另外,由于异常对象的传递方式有三种:传值、传引用和传指针。所以实际上这就导致了有6种不同的组合情况。下面分别阐述之。

  异常对象再次原封不动地被重新抛出

  1、首先讨论异常对象“按值传递”的方式下,异常对象的构造、传递和析构销毁的过程有何不同之处?毫无疑问,在异常被重新被抛出时,前面的一个异常对象的构造和传递过程肯定不会被影响,也即“按值传递”的方式下,异常被构造了3次,异常对象被“按值传递”到这个catch block中。实际上,需要研究的是,当异常被重新被抛出时,这个异常对象是否在离开当前的这个catch block域时会析构销毁掉,并且这个异常对象是否还会再次被复制构造?以及重新被抛出的异常对象按什么方式被传递?看如下例程:

  

 

  通过上面的程序运行结果,可以很明显地看出,异常对象在被重新抛出时,又有了一次拷贝复制的过程,瞧瞧!正常情况下,按值传递异常的方式应该是有3次构造对象的过程,可现在有了4次。那么这个异常对象在什么时候又再次被复制构造的呢?仔细分析一下,其实也不难明白, “异常对象ex_obj1:1”是局部变量;“异常对象ex_obj1:2”是临时变量;“异常对象ex_obj1:3”是第一个(内层的)catch block中的参数变量。当在catch block中再次throw异常对象时,它会即刻准备离开当前的catch block域,继续往上搜索对应的catch block模块,找到后,即完成异常对象的又一次复制构造过程,也即把异常对象传递给上一层的catch block域中。之后,正式离开内层的catch block域,并析构销毁这个catch block域中的异常对象ex_obj1:3,注意此时,属于临时变量形式的异常对象ex_obj1:2并没有被析构,而是直至到后一个catch block处理完后,先析构销毁异常对象ex_obj1:4,再才销毁异常对象ex_obj1:2。整个程序的执行流程如图14-1所示。

  

 

  图14-1异常对象构造和销毁的过程

  下面来一步一步看它的流程,第①步执行的操作,及执行完它之后的状态,如图14-2所示。它构造了一个局部变量形式的异常对象和拷贝复制了一个临时变量形式的异常对象。

  

 

  图14-2 第一步,抛出异常

  由于第①步执行的抛出异常的操作,因此找到了相应的catch block后,便执行第②步复制异常对象的过程,如图14-3所示。

  

 

  图14-3 第二步,复制异常到catch block域

  第②步复制异常对象完毕后,便进入到第③步,离开原来抛出异常的作用域,如图14-4所示。这一步的操作是由系统所完成的,主要析构销毁这个当前作用域已经构造过的对象,其中也包括属于局部变量的异常对象。

  

 

  图14-4 第三步,析构局部变量

  接下来正式进入到catch block的异常处理模块中,如图14-5所示。

  

 

  图14-5 第四步,异常处理模块中

  当在异常模块中再次原封不动地把原来的异常对象重新抛出,那么系统将会继续下一次的查找catch block模块的过程,如图14-6所示。注意,它这里并不会再次复制一个另外的临时异常对象,而只是在新的catch block模块中完成一次异常对象的复制过程。

  

 

  图14-6 第五步,又一次复制异常到catch block域

  同样,在复制完毕异常对象以后。程序控制流又会回到原来的作用域去销毁局部变量。如图14-7所示。注意,这里并不会析构销毁临时变量的异常对象,而只是销毁当前作用域内部的局部变量,如“异常对象ex_obj1:3”。

  

 

  图14-7 第六步,离开前面的catch block作用域,并析构该作用域范围内的局部变量

  再接下来就是进入到又一次的异常处理模块中,如图14-8所示。注意,此时系统中存在

  “异常对象ex_obj1:2”和“异常对象ex_obj1:4”。

  

 

  图14-8 第七步,又一次的异常处理模块中

  最后就是全部处理完毕,如图14-9所示。注意,它先析构销毁“异常对象ex_obj1:4”,再才销毁“异常对象ex_obj1:2”。

  

 

  图14-9 第八步,销毁另外的两个异常对象

  通过以上可以清晰地看出,在“按值传递”的方式下,异常对象的被重新rethrow后,它的执行过程虽然与正常抛出异常的情况虽然有所差异,但是在原理上,它们完全是相一致的,而且异常的rethrow是可以不断地向上抛出,就好像是接力赛一样,同时,每再抛出一次后,异常对象将会被复制构造一次。所以说,这里更进一步说明了异常对象的“按值传递”的方式是效率很低的。但是亲爱的程序员朋友们,大家是否也象那个有点傻气,但好像又有些灵气的主人公阿愚一样,联想到了另外一种有些奇怪的组合方式,那就是如果异常对象第一次是“按值传递”的方式,但第二、第三次,甚至后来的更多次,是否可以按其它方式(如“按引用传递” 的方式)呢?如果可以,那么又会出现什么结果呢?还是先看看示例吧!上面的程序只作了一点改动,如下:

 

哈哈!主人公阿愚非常开心。因为它果然不出所料,是完全可以的,而且结果也合乎情理。异常对象还是只构造了三次,并未因为异常的再次抛出,而多复制构造一次异常对象。实际上,大家已经知道,控制异常对象的传递方式是由catch block后面的参数所决定,所以对于无论是最初抛出的异常,还是异常在catch block块被再次抛出,它们无须来关心,也控制不了。在最初抛出的异常时,完成两次异常对象的构造过程,其中最重要的是临时的异常对象,它是提供向其它参数形式的异常对象复制构造的原型,也即异常在不断接力地被抛出之后,如果上层的某个catch block定义“按值传递”的方式,那么系统就会从这个临时变量的异常对象复制一份;如果上层的某个catch block定义“按引用传递”的方式,那么系统会把引用指向这个临时变量的异常对象。而这个临时变量的异常对象,只有在最后一个catch block块(也即没有再次抛出)执行处理完毕之后,才会把这个异常对象予以析构销毁(实际上,在这里销毁是最恰当的,因为异常的重新被抛出,表明这个异常还没有被处理完毕,所以只有到最后一个catch block之后,这个临时变量的异常对象才真正不需要了)。

  另外,还有一点需要进一步阐述,那就是上层的某个catch block定义“按值传递”的方式下,系统从临时变量的异常对象所复制一份参数形式的异常对象,它一定会在它这个作用域无效时,把它给析构销毁掉。

  2、接下来,讨论异常对象“按引用传递”的方式下,异常对象的构造、传递和析构销毁的过程有何不同之处?其实这在刚才已经详细讨论过了,不过,还是看看例程来验证一下,如下:

  

 

   结果不出所料,异常对象永远也只会被构造两次。所以异常对象“按引用传递”的方式,是综合性能最好的一种方式,效率既非常高(仅比“按指针传递”的方式多一次),同时也很安全和友善直观(这一点比“按指针传递”的方式好很多)。另外,这里也同样可以把“按引用传递”的方式和“按值传递”的方式相混合,代码示例如下:

 

 

  3、最后,讨论异常对象“按指针传递”的方式下,异常对象的构造、传递和析构销毁的过程有何不同之处?其实这种方式不需要过多讨论,因为异常对象“按指针传递”的方式下,异常对象永远也只会需要被构造一次,实际上,它被传递只是一个32bit的指针值而已,不会涉及到异常对象的拷贝复制过程。但是有一点是需要注意的,那就是对异常对象的析构销毁必须要放在最后一个catch block处理完之后,中间层的catch block是决不应该delete掉这个一般在堆中分配的异常对象。

  catch block块处理异常时,又激发了另外一个异常的抛出

  呵呵!表面上看起来,这种情况下会很复杂,因为好像前面一个异常错误还没有被处理完,又引发了另外的一个异常错误,岂不是很麻烦呀!其实不然,系统对这种接力方式的异常重新抛出的处理策略往往很简单,那就是系统认为,当在catch block的代码执行过程中,如果抛出另一个异常,而导致控制流离开此catch block域,那么前一个异常会被认为处理完毕,并释放临时的异常对象,同时产生下一个异常的搜索catch block过程和异常处理的过程等。也即就是说,系统会把这种异常的重新抛出情况,认为是两次分离的异常。虽然它们是连在一起,并能够形成异常的接力抛出,但是处理上,它们完全是被分开进行的。所以说,这种情况下,往往会产生后一次异常对前一次异常的覆盖。

  另一种特殊的形式的异常被重新抛出

  前面我们所讨论的异常被重新抛出,它们都会导致控制流离开catch block模块,也即整个异常的接力处理过程是分层进行的。但实际上,异常的重新抛出后,是可以把它局限于当前的catch block域内,让它逃离不开这个作用域。也即通过在catch block再潜套一个try catch块,示例代码如下:

  

 

  呵呵,通过上面的方式,可以让当前的catch block的处理得以安全保障,防止可能潜在的异常再次出现,而导致上层可能处理不了其它的意外异常,而引发程序崩溃。所以说,C++异常模型的确是非常灵活,功能也非常强大。

  总结

  (1) 异常重新的被抛出,它的处理过程虽然稍微略有些复杂,但是总体上还是比较易于理解的。它更不会影响并破坏前面章节中所讲述过的许多规则,它们的处理策略和思想是相一致的;

  (2) 再次强调,异常对象“按引用传递”的方式是综合性能最佳的方式。

  对C++中的异常处理机制的阐述,到此暂告一个段落,从下篇文章开始,主人公阿愚将继续开阔自身的视野,以对异常处理编程进入一个更加广泛的探讨之中。各位程序员朋友们,继续吧!

boost::rethrow_exception相关的演示程序 然后,在catch块中,将该异常对象重新抛出,同时使用boost::enable_error_info()函数添加额外的错误信息。下面的示例演示了如何使用boost::rethrow_exception()函数来重新抛出异常。使用boost::rethrow_exception()函数可以使程序在处理异常时更加灵活,同时也保留了原始异常信息,便于问题定位和诊断。在main()函数中,捕获重新抛出的异常,并提取了通过boost::enable_error_info()添加的文件名和行号信息。 阅读详情

相关推荐

boost::rethrow_exception相关的测试程序

boost::rethrow_exception相关的测试程序实现功能C++实现代码 实现功能 boost::rethrow_exception相关的测试程序 C++实现代码 #if defined(_MSC_VER) # pragma warning(disable: 4702) // unreachable code #endif #include <boost/exception_ptr.hpp> #include <boost/core/lightweight_test.hpp&g

希望我的博客,能帮上你解决学习中工作中所遇到的问题 531

c++异常

文章目录错误类型异常框架原则:捕获为常引用rethrow(嵌套异常)Exception safety levelsNo-throw guaranteeStrong exception safetyWeak exception safetyNo exception safetynoexcept specifier 错误类型 异常为了处理运行时错误和逻辑错误 #include <exception> for std::exception object #include <stdexcept&

行走 795

C++ `std::throw_with_nested`:C++11 嵌套异常的捕获与报告

这时候,如果我们只能捕获到最外层的异常,而忽略了导致这个异常的根本原因,那就像医生只治标不治本,问题还是会卷土重来。的作用很简单:它创建一个新的异常对象,并将当前正在处理的异常作为它的嵌套异常。是 C++11 中一个非常有用的异常处理工具,它可以帮助你更好地处理嵌套的异常,保留异常发生的上下文信息,简化错误处理逻辑,提高代码可维护性。它的作用是:如果给定的异常对象包含嵌套异常,则重新抛出该嵌套异常;如果该对象包含嵌套异常,则它会重新抛出该嵌套异常,这意味着程序的控制流会跳到最近的能够处理该嵌套异常的。

weixin_41455464的博客 768

重新抛出(rethrow)

有可能单个catch不能完全处理一个异常。在进行了一些校正行动之后,catch可能确定该异常必须由函数调用链中更上层的函数来处理,catch可以通过重新抛出(rethrow)将异常传递给函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个throw:     throw; 空throw语句将重新抛出异常对象,它只能出现在catch或者从catch调用的函数中。如果在处理代码不活动时碰

享受开发技术,享受计算机科学 2322

c++ - rethrow a exception gotchas

As in my prevoius example in  java - common practice to rethrow exceptions, we have seen that it is always good to rethrow an new exception with the old exception as its inner exception, and it is not...

iteye_13882的博客 217

C++异常rethrow

自 http://se.csai.cn/ExpertEyes/200801031114531905.htm 在相遇篇中的《第5 C++异常rethrow》文章中,已经比较详细讨论了异常重新被抛出的处理过程。但是有一点却并没有叙述到,那就是C++异常重新被抛出时(rethrow),异常对象的构造、传递和析构销毁的过程会有哪些变化和不同之处。为了精益求精,力求对每一个细节都深

纯,属虚构 4975

异常Rethrow分析

网上看了一篇类似的分析,仔细看了一下写的不错,现在把代码引用一下:try       {              try              {                     // 抛出一个异常对象                     throw MyException("ex_obj1");              }           

李永亮的专栏 2178

C++异常rethrow

C++异常rethrow】 http://se.csai.cn/ExpertEyes/200801031114531905.htm 在相遇篇中的《第5 C++异常rethrow》文章中,已经比较详细讨论了异常重新被抛出的处理过程。但是有一点却并没有叙述到,那就是C++异常重新被抛出时(rethrow),异常对象的构造、传递和析构销毁的过程会有哪些变化和不同之处。为了精益求精,力求对...

jcair的博客 1084

Swift异常处理:throw和rethrow

Swift异常处理体现了函数式语言的特性,因此我们可以传一个会抛出异常的函数闭包(高阶函数)作为参数传到另一个函数中(父函数),父函数可以在子函数抛出异常时直接向上抛出异常,这时用rethrow关键字表示引用闭包时抛出的异常。throw关键字和Java一样,就是说调用这个函数本身可能会抛出异常rethrow可以看作是throw的子,Swift不把rethrow作为完整的类型。例如一个函数不能同

西涛offbye-移动全栈技术博客 6266

C++异常处理(

 和其它很多程序员一样,本书的主人公阿愚也是在初学C++时,在C++的sample代码中与异常处理的编程方法初次邂逅的,如下: // Normal program statements ...try { // Execute some code that might throw an exception. } catch( CException* e ) { // Handle the

Joe_wang的专栏 1880

使用lambdaquery() 报空指针异常_C++异常处理

背景传统的C代码中,当我们处理异常情况的时候,我们使用的是函数返回值,或者是非局部的errno变量来指示一个错误或者异常的发生。而在C++中,我们引入了try、throw、catch关键字组成的异常处理机制,而且已经习以为常。但是,在异常处理这类问题中,为什么函数返回值不够好用,以至于我们需要新引入try catch呢?原因大概这几方面:传统的代码中,处理异常的代码和正常的逻辑代码交织在一起,代码...

weixin_39972996的博客 764

c++以exception_ptr传递异常

C++11起,C++标准库提供一个能力:将异常存储于类型为exception_ptr的对象中,稍后才在其他情境(context)中处理它们:

新法的博客 1927

C++ —— 异常

摘要:本文系统介绍了C++异常处理机制。异常处理通过抛出对象实现错误传递,将问题检测与处理分离。抛出异常后,程序会沿调用链查找匹配的catch子句(栈展开),未匹配则终止程序。异常对象类型匹配遵循特定规则,允许派生类向基类换。文章还讨论了异常安全、析构函数中的异常处理注意事项,以及C++11的noexcept规范。通过多个示例展示了异常捕获、重新抛出、继承体系设计等实际应用场景,并对比了C++17对异常对象构造的优化。最后指出noexcept运算符可用于编译时检查表达式是否会抛出异常

watergraphically的博客 403

C++异常处理入门

本文详细介绍了C++异常处理机制,包括异常的概念、抛出与捕获规则、匹配处理代码的查找过程以及异常安全等问题。文章通过具体代码示例演示了异常传播流程,分析了继承体系下的多态捕获方式,并讲解了异常重新抛出、异常规范等高级用法。最后指出标准库提供了exception基类及其派生类体系,建议在主函数中捕获exception基类来处理各类异常异常处理机制使程序能更好地分离错误检测与处理逻辑,是构建健壮程序的重要工具。

m0_65245374的博客 630

c++11标准新特性---异常处理

这些新特性使得C++异常处理更加灵活和方便,同时提供了更多的上下文信息,有助于开发更健壮和可靠的软件。),它是一种指向异常对象的智能指针,允许在不立即重新抛出异常的情况下保存异常状态,以便稍后处理。这些异常类可用于捕获并处理相应的错误情况,从而使程序在出现异常情况时能够进行适当的处理或恢复。类,它允许在异常处理期间保留异常的原始信息,并在稍后重新抛出异常时提供更多上下文信息。函数,它允许在捕获异常后重新抛出异常指针,以便在其他位置重新引发异常。函数,它可以将异常换为异常指针,方便异常的保存和处理。

xxxiaobai707的博客 851

第4 C++异常处理和面向对象的紧密关系()

第4 C++异常处理和面向对象的紧密关系() 2010年01月05日   如果有人问起C++和C到底有那些本质上的不同点?主人公阿愚当然也会有自己的一份理解,他会毫不犹豫回答出:“与C相比,C++至少引入了两项重要技术,其一就是对面向对象的全面支持;还有一项就是C++优良的异常处理模型”。是的,这两项技术对构建出一个优良的可靠复杂的软件系统都太重要了。可这两项技术之间又有何关系呢?...

hzx535lb 214

[] C++ try catch() throw 异常处理

原文地址 其它很多程序员一样,本书的主人公阿愚也是在初学C++时,在C++的sample代码中与异常处理的编程方法初次邂逅的,如下: //Normalprogramstatements ... try { //Executesomecodethatmightthrowanexception. } catch(CExce...

weixin_30920091的博客 92

C++异常

有时catch到⼀个异常对象后,需要对错误进⾏分类,其中的某种异常错误需要进⾏特殊的处理,其他错误则重新抛出异常给外层调⽤链处理。例如,如果栈帧中有一个动态分配的内存对象,其析构函数会释放这块内存,避免内存泄漏。如果在当前函数中没有捕获重新抛出的异常,程序会继续回退,直到找到合适的处理代码,或者最终导致程序终止。栈展开的主要目的是确保在异常发生时,程序能够安全地回退到异常处理代码处,同时清理沿途的资源。栈展开是指当异常发生时,程序的执行流程从异常发生的点开始,逐层回退到调用栈的上层,直到找到合适的。

2501_94114815的博客 171
上一篇: [转]数据库设计三大范式应用实例剖析
下一篇: [转]C++异常机制的实现方式和开销分析
candj
博客等级 码龄19年 5粉丝 39原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值