C++异常(1): 三大缺陷

1) 性能损失

a) 正常路径性能损失, 一般而言, 较小.

b) 异常路径性能损失较大, 异常处理需要stackunwinding(栈展开), 搜寻能够处理异常的catch块, 保证抛出对象生命周期(注:C++允许抛出任何对象)

c) 异常处理代码导致目标文件体积增大 (大约多10~20%,某些统计为20%- 40%), 这在一些嵌入式应用上是无法容忍的.

2) Exception Specification (异常声明)有明显缺陷

C++11之前的版本:

    void Foo();  // 可能抛出任何一种异常, 也可能不抛出异常

    void Foo()throw();  // 不抛出异常, 但实际上无法保证.

    void Foo() throw(std::runtime_error);            // 只抛出runtime_error, 但实际上无法保证

C++11废弃了这种异常声明, 简化为二元模式:

    void Foo();  // 可能抛出异常, 也可能不抛出异常,向下兼容

    void Foo() noexcept(false);  // 可能抛出异常,也可能不抛出异常

    void Foo() noexcept;             // 保证不抛出异常,但实际上无法保证.

之所以无法保证程序符合异常声明, 是因为编译器无法执行编译期的检查.在C++11中,程序不会向上传递捕获的有noexcept(true)声明的函数抛出的异常,而直接执行terminate,从而有利于代码的性能优化.

异常声明缺陷导致的严重问题之一, 是通过静态代码检查将无法确定程序执行流,这是很多C++项目禁止使用异常的一个原因.相较而言,Java在这方面做得非常好.

3) 异常引入的各种陷阱

a) 局部动态资源泄露陷阱 (使用RAII可以解决)

b) 当一个异常抛出时, 不允许在异常的处理函数中再抛出一个新的异常,否则unexpected直接被调用

c) 在搜寻异常处理块时, 找到第一个满足抛出对象类型的语句块时就开始处理,不是找到最优的,因此在继承异常对象体系中,最具体的类型应该放在开始,最抽象的类型应该放末尾.

d) 在进行stack unwinding的过程中, 除非最终能找到一个能处理该异常的catch语句,局部资源的析构函数才会被调用,也就是说只有stackunwinding停止了, 局部资源的析构函数才被调用, 这种情况下RAII对资源泄露也无能为力(此陷阱有可能是编译器相关的,g++ 5.4测的此结果)

e) C++ EH机制自动维护抛出对象的生命周期, 在stackunwinding的过程中, 只有一个抛出对象. 如果catch参数使用值传递,则catch中处理的对象是副本,这样任何改动都不影响原有的抛出对象,有时这可能不是期望的结果,比如,该catch块记录了一些信息后继续re-throw,这时抛出的是原有对象,而非操作的副本.

由于明显的缺陷问题, 大部分C++项目不允许使用异常,在g++编译器上,可以通过编译参数-fno-exceptions来禁止代码使用异常.不幸的是,这个选项只是一个静态代码检查(禁止使用throw/try/catch语句而已),对于其他已使用异常目标文件是无效的,因此程序在运行中仍然可能抛出异常,除非使用该选项重新编译所有用到目标文件(包括系统库文件如glib等)且要求目标文件的实现中支持-fno-exceptions.

如果没有编译选项-fno-exceptions, 则宏__cpp_exception有定义,否则没有定义.可以据此编写兼容异常处理的代码.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值