浅淡C++ RAII

RAII(Resource Acquisition Is Initialization),也成为“资源获取就是初始化”。名字听起来古怪,其实倒是比较容易理解的。

也就是说,将资源封装在一个对象里,在对象创建时即获取其所需要的资源,在对象析构时即释放其获取的资源。

这样,就用一个对象的生存期来保证了资源的释放。也就是说,把资源释放的时机交给了编译器来帮你确定。

比如,前一篇关于《:ref:`C++异常处理的是与非<cpp-exception-top>`》的文章,提到的例子:

我们可以封装出一个简单的FileGuard的类,在FileGuard构造函数里进行open,在FileGuard的析构里进行close,如下:

然后,把do_something改成如下:

这就避免了foo抛出异常而导致的资源未释放的问题。

有人说,在java/python/C#等语言中,我们可以利用try/catch/finally来搞定这种需求,比如:

对于这个示例,确实是这样,可是,考虑以下示例:

假设open可能出现异常,如果我们不把open包进try语句块里,则当src的open操作出现异常,我们可以直接让它抛出给上层,但当dest出现异常,则会出现src未正常关闭的问题。

我们如果把open包在try语句块里,则,则finally中又必须去判断到底是第几个语句时出了异常,我该不该对src/dest进行close...

这时候,RAII又是一剂解救我们的良药。

如下简单又极具可读性的代码就能很完美的解决问题:

与上面例子比较类似的东西很常见,如boost::thread库里的scope_lock就是一个典型的实现。

另外,我们经常说的智能指针(如scoped_ptr,shared_ptr)也是RAII的一个经典案例,只不过它管理的资源较为特殊–它在用一个(或多个)变量的生存期自动管理另一个变量的生存期。

那么RAII就是为了编写异常安全的代码而生的吗? 不,不是。在不使用异常的代码里RAII也是很有市场的~

一个超级常见的场景就是你需要在一个函数结束调用前把其申请的所有资源释放掉,但函数逻辑较为复杂,代码里有一堆一堆的return,这时再小心的人也很容易忘记在某次返回前进行资源释放的操作。而且资源释放又是一个比较固定的代码,每次return前copy一份相同的代码明显会降低程可维护性。常见的做法是在每个需要返回的地方使用goto语句跳转到资源释放的语句。也有人为避免goto而使用do{...}while(0);包裹整个流程,并在需要return处写个break,然后在后面进行资源释放。即使这样,代码也看起来比较奇怪。而RAII就是一个完美的方案。

一个典型的RAII类的代码如下:

说到现在,估计读者都明白了RAII是什么,以及有什么用了,不过不少同学会嘀咕,这家伙用起来好麻烦啊,我想释放的资源都得写到一个类里,每次用都得重新生成一个类,然后把需要操作的变量利用构造函数传递到类里作为成员,然后在析构函数里把要操作的变量干掉。特别是当要操作的变量比较多时,这类里一堆堆的成员。看起来想必也是极丑陋的。

是的,这样用确实比较丑陋,于是出现了各种的解决方案,比如,构造函数传入无参的函数,然后把需要操作的变量bind到一个操作函数,然后把它们传给RAII的Guard类。

boost里就提供了一种方案,最终使用时代码如下(可能是比较旧的版本,新版本的boost里代码可能略有不同):

就样一种方案,就使得我们很清晰很简单的来使用RAII,就像语言原生的一种语法一样。(话说go语言里原生就支持这种东西,它的defer语法应该就是基于RAII思想所设计的)

如果你的编译器支持C++ 0x (或称作c++11),你就可以利用tr1::function和lambda表达式轻松的搞出一个类似的Guard类,如:

然后,你就可以利用lambda表达式来这么着释放资源了:

最后感慨一下前段用python,想找一个类似于scope_lock的类,找了半天愣是没找到,后来我仔细想了想,忽然地发现由于进行垃圾回收的语言中变量的生存期不是由程序员控制的,这就导致了RAII机制较难很自然的实现。(所以python中提供了with语法,C#中提供了using语法,但在我刚才提到这种场景下,定义一个RAII类,却必须让用户在using中使用才能有效,感觉总是不大友好)

由于没有Guard,而又使用了异常,导致我那段python代码最后看起来很难看。

所以,既然你用了没有GC的语言,就享受没有GC到来的好处吧~

参考资料:

[1]《boost手册》

[2]《C++11(及现代C++风格)和快速迭代式开发》 http://mindhacks.cn/2012/08/27/modern-cpp-practices/

返回顶部


转载请指明出处:http://blog.acmol.com

This Page