关于 C++ 中的 RAII

名字起得不好,实际上是想体现资源管理和对象生命周期绑定,在构造函数里获取资源,在析构函数里释放资源。

RAII 的有趣之处在于利用栈对对象的生命周期进行管理,也就间接实现了对资源的管理,因为进入作用域时会把上下文(对应的数据结构是栈帧 stack frame)压栈,离开作用域时相应地会出栈,压栈时调用构造函数,实现了对资源的获取,出栈时调用析构函数,实现了对资源的释放。

获取资源是相对容易的,即使失败了也无非就是没有获取成功,哪怕因此导致程序退出,也不至于产生太严重的后果。而释放资源出现问题的后果可能会严重得多,可能导致“永久性”的资源泄漏,比如内核和文件系统的资源,如IPC(信号量、消息队列、共享内存)和文件。这套机制在确保资源正确释放上做出了很多的设计。

我们不妨设想一下,有哪些导致资源释放出现问题的场景?

  1. 忘记释放;
  2. 对象切片 Object slicing;
  3. 程序出现异常;
  4. 自定义内存管理机制;
  5. ……

第一种第二种是自己写法的问题,C++ 针对第一种问题提供了智能指针等解决方案,第二种问题是 copy 时的信息丢失,属于是没有处理好多态性,需要自己注意,第四种比较少见而且很难一概而论,最常见的还是异常。所以,RAII 针对异常有一套专门的机制:栈展开(stack unwinding)。

应该说这个词还是比较形象的,不停地寻找可以处理当前异常的 catch 语句,如果在当前作用域中没有找到,就出栈然后在上一级中寻找,类似于把栈一层一层展开的过程。从我个人的角度,这个机制是在程序出现异常时尽量先调用析构函数,尽可能确保把资源释放出去。在我个人的体验中,甚至在 try-catch 的过程中,甚至会先触发析构再触发 catch,把这个原则贯彻到底。

不过这个机制也不是一直有效。因为异常从本质上来说还是一种程序退出的机制,大部分时候问题还是出在程序退出机制本身。一般来说,会有这么几种程序退出的情况:

  1. main 函数 return
  2. std::terminate
  3. std::exit
  4. std::abort

然而很不幸的是,除了在 main 函数中正常退出之外,其他几种都不能确保资源完全释放。具体来说:std::exit 只释放静态区的数据,而 std::abort 则是完全不释放。为什么没有 std::terminate?因为这玩意默认调用了 std::abort。可以参考这篇文章:https://www.cnblogs.com/zhenjing/archive/2011/07/06/stackunwinding.html

这也是不建议写在析构函数中抛出异常的代码,以及在异常中抛出异常的代码的原因,因为可能会导致栈展开机制失效,从而阻碍资源完全释放。不过为什么大部分时候也没啥事呢?因为有 OS 兜底,进程退出时会把进程占用的资源释放。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值