09 | exception:怎样才能用好异常?

  • 为什么要有异常
    • 错误理解
      • 一发生异常,就是"了不得的大事"
    • 正确理解
      • 只是C++为了处理错误而提出的一种解决方案;它可以穿透调用栈,传播到别的地方处理
    • 在c++之前,处理异常的手段是"错误码"
      • 很直观,但正常和异常混在一起,看起来很乱
      • 有时候错误逻辑比正常的更复杂,看了半天可能会忘记它当初的作用
        • 对比下预处理代码和C++代码混在一起的情形
      • 更大问题:它是可以被忽略的--你可以完全不处理错误
    • 异常就是针对上述缺陷设计的
      • 3个特点
        • 1.异常的处理流程是完全独立的
          • throw之后就可以不用管了,错误处理代码集中在catch中.
        • 2.异常不能被忽略,必须被处理
          • 如果不写catch,那么它会一直向上传播,知道知道能够处理的catch块
          • 如果确实没有catch,那么会导致程序立即停止运行
        • 3.可以用在错误码无法使用的场合
          • 比如构造,析构,没有返回值,或者返回值无法表示错误;而全局的erno不优雅
  • 异常的用法和使用方式
    • C++中对异常定义宽松,任何类型都可以用throw抛出,比如错误码,错误信息(char *, string)
      • 不建议
    • C++的异常类型体系,定义在<stdexcept>头文件中
    • 继承深度超过3层,就有点过度设计.最好选择上面的第一层或第二层作为基类,不要在加深层次
    • 最好不要直接使用throw关键字,而是要封装成一个函数,这里new,delete关键字类似的道理--中间层有更多的可读性,灵活性和安全性
    • 允许编写多个catch块,但异常只是按照catch块在代码里的顺序依次匹配,而不会找最佳匹配
      • 建议只用一个catch块,防止写错顺序进入不想进的catch
    • catch的入口参数使用const &,避免对象拷贝的代价
    • function-try-catch用法,将整个函数视为一个大try块
      • try
  • 谨慎使用异常
    • 异常的成本:异常的抛出和处理需要特别的栈展开,如果异常位置很深,但没有被及时处理,或者频繁地抛出异常,会对运行性能产生很大的影响.此时程序忙着处理异常
    • 均衡饮食
      • 正常返回值或不太严重,可以重试或恢复的错误,不建议使用,把他们轨道正常流程中
        • 比如字符串未找到(不是错误),数据格式不对(轻微错误),数据库正忙(可重试错误)
      • 应当使用的准则
        • 1.不允许被忽略的错误
        • 2.极少数情况下才发生的错误
        • 3.验证影响正常流程,很难恢复到正常状态的错误
        • 4.无法本地处理,必须"穿透"调用栈,传递到上层才能被处理的错误
        • 举例
          • 构造函数内部初始化失败,无法穿件,下面的逻辑无法进行了
          • 读写文件,通常文件系统很少会出错,总会成功,如果用错误码来处理不存在,权限错误等,就显得太啰嗦,此时应使用异常
          • 反例
            • socket通讯收发失败后果很严重,但出现频率太高了,出于性能考虑,应检查错误码重试比较合适
  • 保证不抛出异常
    • 预编译指令noexcept
      • 用来修饰函数,告诉编译器:这个函数不会抛出异常.编译器就可以对函数优化,不去加那些栈展开的额外代码,消除异常处理的成本
      • 和const一样(告诉编译器我不会改成员变量),noexcept也是放在函数后面
      • 注意:不是强保证,有可能抛出异常
      • 它的意义:我承诺不抛出异常,我也不想处理异常,如果真有异常发生,请让我死的干脆掉,直接崩溃
      • 那何时应该加上它呢?
  • 小结
    • 为了保证出现异常时资源会正确释放,就必须禁用裸指针,改为智能指针,用RAII来管理内存
    • C++没在语言层面提出更好的机制,所以要在编码阶段写好文档和注释;说清楚那些函数,在什么情况下会抛出怎样的异常,应如何处理,加上"软约束"
  • 课外小贴士
    • boost.exception不需要定义复杂的数据结构,就可以像一场对象添加任意的信息
    • C++中异常处理没有finally
    • 一般认为重要的构造函数(普通构造,拷贝构造,转移构造),析构函数尽量声明为noexcept,优化性能而析构函数则必须保证绝不会抛异常
  • 课后讨论
    • 明确要抛出异常的函数,声明为noexcept(false)
    • 给别人使用的库最好用错误码的方式,不要把异常抛给外面处理,通俗点讲就是eat your own dog food;外界不了解库的工作机制,也不知道如何处理才好,或者根本就不知道你会抛异常.市场上的sdk都是报错误码;boost是基础开发库,不是给客户用的.
    • 涉及磁盘操作的最好使用异常+调用栈,涉及业务逻辑的最好利用日志+调用栈,涉及指针和内存分配的还是用日志+调用栈吧,这种coredump一般是内存泄露和内存不够引起的
    • 在C++里数组越界不一定会引发异常,可能正常也可能直接crash.try只能捕获可控的异常,不是万能的

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodingLife99

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值