(本节内容重点描述RAII,详见Effective C++ 14。)
问题实例
假设目前我们需要为某宠物店编写软件,工作是读取宠物店建立的档案文件,然后对动物进行适当处理。
合理方案是建立一个pure abstract class
“ALA”(adorable little animal)。然后为小狗小猫建立派生类,一个virtual函数processAdoption负责处理各个不同种类的小动物:
1 | class ALA{ |
我们通过从文件中读取到的信息建立一个puppy或kitten对象,这很适合工厂模式(虚构造函数):
1 | ALA * readALA(istream& s); |
程序的核心部分如下:
1 | void processAdoptions(istream& dataSource){ |
每一次读取完必须删除pa,如果不删除堆对象会导致资源泄漏。
问题剖析
倘若processAdoption函数抛出了异常,这将直接导致后续语句被跳过,pa没有被删除,资源泄漏由此产生。
一提到异常,大多数人的第一反应是使用try catch去捕获:
1 | void processAdoptions(istream& dataSource){ |
虽然问题确实解决了,但是我们却使得原有程序冗余且低效,反正无论如何我们都要执行清除操作,何必要写多次呢?
RAII
我们可以把总被执行的delete放入process函数内的局部对象的析构函数里,当process返回时局部对象被释放,因此delete操作也完成了。
具体方法是用一个smart-ptr.本次实例使用auto_ptr代替raw指针:
1 | void processAdoptions(istream& dataSource){ |
显然这是一个标准的RAII,用一个对象存储需要被自动释放的资源。
RAII实例
RAII并非只是适用于智能指针或者堆对象,其本意是用对象来管理一切可能存在泄漏的资源的分配与释放。以一个可能发生资源泄漏的GUI程序举例:
1 | void displayInfo(const Information& info){ |
如果在w对应的window中显示信息时,一个异常被抛出,w所对应的window将被丢失,就象其它动态分配的资源一样。
解决方法是建立一个类,构造函数接受资源,析构函数释放资源:
1 | class WindowHandle { |
使用了RAII的display函数如下所示:
1 | void displayInfo(const Information& info){ |
总结
资源应当被封装在对象内,遵循该原则可以避免在异常发生时泄漏资源。