- 热点
析构抛异常
时间:2010-12-5 17:23:32 作者:综合 来源:探索 查看: 评论:0
内容摘要:C++标准里明明白白写着:析构函数默认不抛异常,但你要是真让它抛了,程序直接调`std::terminate`。这事儿我踩过坑,而且不止一次。 去年维护一个老项目,内存池模块的析构函数里有个`dele
C++标准里明明白白写着:析构函数默认不抛异常,析构但你要是抛异真让它抛了,程序直接调`std::terminate`。析构这事儿我踩过坑,抛异而且不止一次。析构 去年维护一个老项目,抛异内存池模块的析构析构函数里有个`delete`操作,指针恰好是抛异野的,崩了。析构当时第一反应是抛异查日志,结果日志写了一半,析构析构函数抛出异常,抛异缓冲区没刷新,析构啥也没留下。抛异你猜怎么着?析构我花了两天时间定位,最后发现是析构函数里一个第三方库的清理函数在特定条件下会抛`std::runtime_error`。这库文档里写着“可能抛出异常”,我压根没当回事。 析构函数抛异常的本质问题在于:异常处理机制要求栈展开时调用析构函数,如果析构函数自己又抛异常,两个异常同时存在,标准委员会直接拍板——调`terminate`,程序死给你看。没人能救。 有人会说,那我在析构函数里用`try-catch`包住不就行了?对,包住是基本操作,但包住之后呢?你吞掉异常,资源可能没释放干净;你记录日志,日志系统本身可能已经处于异常状态。我见过最离谱的写法是析构函数里`throw`,然后外面套一层`catch(...)`,看起来安全了,实际上析构函数执行到一半,对象状态已经损坏,后续内存回收直接乱套。 换个角度想,析构函数本来应该做的是“清理”而非“业务逻辑”。你把可能失败的操作塞进析构,等于把不确定性埋在了程序最不该出错的地方。比如关闭文件,`fclose`返回错误码,你可以在析构里检查然后写日志,但绝不该让这个错误以异常形式冒出去。我现在的原则是:析构函数里只做两件事——释放内存,关闭句柄。任何可能抛异常的操作,要么提前在正常流程里做完,要么用`noexcept`包装并记录错误。 还有个细节容易被忽略:容器析构时,元素析构函数抛异常,那容器自身的析构也会挂。比如`std::vector`析构时,如果某个元素的析构抛了,整个`vector`的析构就中断,剩余元素直接泄漏。这问题在嵌入式开发里尤其致命,内存本来就紧张,泄漏几个对象直接死机。 说白了,析构函数抛异常是设计缺陷的体现。你写代码时如果觉得“这里可能出错,但不知道怎么办”,那就该在更高层处理,而不是丢给析构。我见过一个老手写的网络库,连接对象的析构函数里居然有`send`操作,美其名曰“优雅关闭”,结果对方主机断网,`send`超时抛异常,整个服务线程崩了。后来改成显式调用`close()`方法,析构函数只做内存回收,问题再没出现过。 所以我的建议很直接:析构函数要么不写,要写就写绝对安全的代码。如果非要在析构里做可能失败的事,那就吞掉异常,记录到全局错误日志,但别让异常逃出去。程序可以带着错误继续跑,但绝不能在析构时崩溃——那等于在撤退路上埋地雷,炸的是自己人。