C++内存管理之自动化的资源回收(RAII)

/ 0评 / 0

Published by orzz.org(). (https://orzz.org/cpp%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86%e4%b9%8b%e8%87%aa%e5%8a%a8%e5%8c%96%e7%9a%84%e8%b5%84%e6%ba%90%e5%9b%9e%e6%94%b6raii/)

资源回收一直是写C++代码时需要考虑的重点内容之一.比如在出口点较多的函数中,若不使用一些技巧,仅靠机械的手动方式管理资源,往往会导致资源管理的代码与实际的函数逻辑相互纠缠.这样的代码不仅容易出错,维护起来也颇为头疼.

比如下面的这个函数:

仅仅是一段简单的,以HTTP协议从网络上获取文件的函数,代码总共不超过70行.

可是看看这段代码,其中用于资源释放的地方就有5处.不仅大部分代码是相同的,按这个写法,每个函数的出口点都需要罗列一遍前面获得的资源句柄.

仅仅一段简单的HTTP代码就导致了5处必须的资源释放,要是函数逻辑再复杂一点,判断条件再多一点(比如加上几段异常处理),势必会导致代码的臃肿...让人气愤的是,这些臃肿的代码不仅形式雷同,连调用原因都是基本一致的:因为函数要退出了嘛,必须回收局部资源.

有没有什么好办法可以自动回收这些资源呢?C++虽然总要记得释放指针,不过栈上定义的临时变量似乎从来不需要手动去管它们的内存呀.

这就是了.局部直接定义的变量是在栈上的,按C++的语义,当程序执行过了这个局部区域以后(一般是一对大括号的范围)这段临时分配出的资源会被系统自动回收.

那么如何让我们的局部句柄里的资源能够定义到栈上呢?当然,直接强迫系统让栈来管理不知会有多大的资源是不可能的.这时候我们可以用RAII来解决这个问题.

RAII的通常做法,是在一个临时对象构造时传入需要自动释放的资源,然后...就不用管了.这个临时对象不论在这个局部区域的哪个入口点都会被系统自动回收,这时这个对象的析构过程会自动帮我们把需要释放的资源干净的回收掉.

不过很多时候,我们不一定能够在临时对象构造时就传入所有的待释放资源,给这个临时对象加个添加新资源的接口是个简单的选择.

为了改进这段代码,我们需要先定义一个清理类:

这个里面用到了STL里的list容器,以便管理多个资源句柄.

下面是改进过的文件下载函数:

新写的代码明显逻辑清晰了很多,繁杂的网络句柄释放不见了,仅仅需要考虑的就是文件句柄的释放.

那么,有没有办法将文件句柄也纳入自动管理的范畴呢?再添加一个类似的清理类?如果这样似乎又在另一个方向上面临代码冗余的问题.

有两个解决这个问题的方法.一是使用统一的基类作为CCleaner的清理逻辑,而将特殊的部分用子类派生,或使用类模板来处理.这样的话其实仍然需要重写一部分不一样的逻辑,如上面的例子,在关闭句柄时调用的函数不一样;二则是将不同的部分交给外部完成,比如在添加资源句柄的时候自定义该资源的清理函数.

我们采用第二种方法改写CCleaner:

采用结构体定义CCleaner的内部存储结构,结构体内部将资源指针与清理函数指针相关联.这样不论是何种资源,只要其清理接口属于此类定义方式,均可以使用这个清理类做自动化清理.

下面是再次改进过的文件下载函数:

此时,我们可以专注于业务逻辑的处理,而将资源管理的任务交给清理类全权处理.

其实掌握了此类技巧后,只要是需要在过程退出时完成的操作,都可以自动完成了,比如多线程里的局部加锁解锁;函数内对文件访问后需要在退出函数时还原文件读取位置等等.

比如一个自动的文件位置还原类:

只需要定义IFileObject,并实现相应的接口就可以满足各种文件对象的需求.

Published by orzz.org(). (https://orzz.org/cpp%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86%e4%b9%8b%e8%87%aa%e5%8a%a8%e5%8c%96%e7%9a%84%e8%b5%84%e6%ba%90%e5%9b%9e%e6%94%b6raii/)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据