“异常处理”学习小结

        在我经历过的项目中,很少使用异常处理;对于问题的调试与追踪,基本上都是基于错误码和日志信息。
        这里的学习总结来自于<<C++编程思想 第2卷>>和网络,有很多问题的理解还需要实践经验。

在C语言中处理异常的3种方式:
        1) 在函数中返回错误信息;
        2) 使用标准C库中的信号处理系统,由函数signal()和raise()实现;
        3) 使用标准C库中的非局部跳转函数:setjmp()和longjmp()。

如果在C++中使用C的错误处理方式将会遇到的问题:
        信号处理方法signal()/raise()和函数setjmp()/longjmp()并不调用析构函数,所以对象不会被正确清理。
(顺便说下goto: 使用goto跳入某个作用域,或者使用longjmp()跳出某个作用域,然而这个作用域的栈中有某个对象需要析构时,程序的行为是不确定的)

C++中异常处理流程:
        1.设置一个try块;
        2.在try块中用关键字throw抛出异常;
        3.紧跟try块用异常处理器catch处理异常。

C++异常处理最大的用处:当异常抛出时,程序将做恰当的清理工作。

新名词解释:
        栈反解(stack unwinding):异常发生之前创建的局部对象自动被销毁。
        RAII(Resource Acquisition Is Initialization, 资源获得式初始化):将资源分配成局部对象生命周期的一部分,如果某次分配失败了,那么在栈反解得时候,其他已经获得所需资源的对象能够被恰当地清理。RAII 的优势在于将对象的生命期管理与其他资源(锁、文件、网络连接等等)的管理整合,然后通过“smart pointer”一并解决,这是 C++ 独一无二的优势。
        异常规格说明(exception specification):C++提供一种语法告诉使用者函数会抛出的异常,这样使用者就能正确处理这些异常。当编写可能抛出异常的函数时,最好考虑使用异常规格说明;它提醒使用者来编写异常处理代码以及处理什么异常。异常规格说明主要是为非模板类准备的。

注意事项:
        对象或者是指向派生类对象的引用都会与其基类处理器匹配。(如果异常处理器针对对象而不是针对引用的,这个异常对象将会被“切割”,被截取成基类对象)
        如果没有任何一个层次的异常处理器能够捕获某种异常,一个特殊的库函数terminate()会被自动调用。默认情况下,teminate()调用标准C库函数abort()使程序执行异常终止而退出。通过使用标准的set_terminate()函数,可以设置自己的terminate()函数。
        在构造函数中抛出异常;不要在析构函数内部触发异常。
        析构函数不能抛异常原因:
        1.如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
        2.通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

异常安全(exception safety):
        异常安全代码能够使对象保持状态的一致性而且能够避免资源泄露。
        其他支持异常的语言几乎都有垃圾回收器(garbage collection),抛异常就抛了,不用担心析构,有垃圾回收器管着。只有 C++ 才有异常安全需要考虑,其他支持异常的语言都没有这一概念。
        异常安全是申请资源要在异常下正确释放;这不仅仅是内存,还包括文件、网络 等资源。 

避免使用异常情况:
        1.不在异步事件中使用异常;异常依赖于程序运行栈上的动态函数调用链,异步事件由完全独立的代码来处理,这些代码不是正常程序流程的一部分。
        2.不要在处理简单错误的时候使用异常;
        3.不要将异常用于程序流程的控制;
        4.不要强迫自己使用异常;
        5.新异常,老代码。


使用异常情况:

        1.简化,如果建立的错误处理模式使事情变得复杂并且难以使用,异常可以使错误处理更加简单有效;
        2.使建立的库和程序更安全;
        3.在构造函数中使用异常处理(构造函数没有返回值,但是构造函数中异常处理后,在析构时麻烦重重);
        4.让dynamic_cast<Derived&>(baseReference) 能报错,因为没有空引用;
        5.让操作符重载(overloaded operator)能报错,operator的返回类型往往无法包含错误码,例如operator=()返回的是 Type&;
C++也是唯一一个变量赋值有可能会抛异常的语言,例如 Person s; s = getPersonById(someId);,那么即便 getPersonById() 不抛异常也不能保证上一句赋值不抛异常。)

异常处理开销:

        当异常被抛出时,将造成运行时开销;原因是:一个throw表达式就像一个特殊的系统函数调用,它接收异常对象作为参数并且沿着执行调用链向上回溯;为了完成这项工作,编译器需要在栈上放置额外信息来辅助栈反解过程。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值