9.使用析构函数防止资源泄漏

(本节内容重点描述RAII,详见Effective C++ 14。)

问题实例

 
假设目前我们需要为某宠物店编写软件,工作是读取宠物店建立的档案文件,然后对动物进行适当处理。
合理方案是建立一个pure abstract class
“ALA”(adorable little animal)。然后为小狗小猫建立派生类,一个virtual函数processAdoption负责处理各个不同种类的小动物:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ALA{
public:
virtual void processAdoption() = 0;
...
};
class Puppy:public ALA {
public:
virtual void processAdoption();
...
};
class Kitten: public ALA {
public:
virtual void processAdoption();...
};


我们通过从文件中读取到的信息建立一个puppy或kitten对象,这很适合工厂模式(虚构造函数):
1
ALA * readALA(istream& s);

程序的核心部分如下:
1
2
3
4
5
6
7
void processAdoptions(istream& dataSource){
while (dataSource) {
ALA *pa = readALA(dataSource);
pa->processAdoption();
delete pa;
}
}

每一次读取完必须删除pa,如果不删除堆对象会导致资源泄漏。


问题剖析

 
倘若processAdoption函数抛出了异常,这将直接导致后续语句被跳过,pa没有被删除,资源泄漏由此产生。
一提到异常,大多数人的第一反应是使用try catch去捕获:

1
2
3
4
5
6
7
8
9
10
11
12
13
void processAdoptions(istream& dataSource){
while (dataSource) {
ALA *pa = readALA(dataSource);
try {
pa->processAdoption();
}
catch (...) {
delete pa;
throw;
}
delete pa;
}
}

虽然问题确实解决了,但是我们却使得原有程序冗余且低效,反正无论如何我们都要执行清除操作,何必要写多次呢?


RAII

 
我们可以把总被执行的delete放入process函数内的局部对象的析构函数里,当process返回时局部对象被释放,因此delete操作也完成了。
具体方法是用一个smart-ptr.本次实例使用auto_ptr代替raw指针:

1
2
3
4
5
6
void processAdoptions(istream& dataSource){
while (dataSource) {
auto_ptr<ALA> pa(readALA(dataSource));
pa->processAdoption();
}
}

显然这是一个标准的RAII,用一个对象存储需要被自动释放的资源。


RAII实例

RAII并非只是适用于智能指针或者堆对象,其本意是用对象来管理一切可能存在泄漏的资源的分配与释放。以一个可能发生资源泄漏的GUI程序举例:

1
2
3
4
5
void displayInfo(const Information& info){
WINDOW_HANDLE w(createWindow());
...//在w对应的window中显示信息
destroyWindow(w);
}

如果在w对应的window中显示信息时,一个异常被抛出,w所对应的window将被丢失,就象其它动态分配的资源一样。
解决方法是建立一个类,构造函数接受资源,析构函数释放资源:
1
2
3
4
5
6
7
8
9
10
11
class WindowHandle {
public:
WindowHandle(WINDOW_HANDLE handle): w(handle) {}
~WindowHandle() { destroyWindow(w); }
operator WINDOW_HANDLE() { return w; } // see below
private:
WINDOW_HANDLE w;
//防止拷贝
WindowHandle(const WindowHandle&);
WindowHandle& operator=(const WindowHandle&);
};

使用了RAII的display函数如下所示:
1
2
3
4
void displayInfo(const Information& info){
WindowHandle w(createWindow());
...//在w对应的window中显式信息;
}


总结

 
资源应当被封装在对象内,遵循该原则可以避免在异常发生时泄漏资源。