Ok,你不用对所有的指针说再见,但是你需要对用来操纵局部资源(local resources)
的指针说再见。假设,你正在为一个小动物收容所编写软件,小动物收容所是一个帮助小狗
小猫寻找主人的组织。 每天收容所建立一个文件, 包含当天它所管理的收容动物的资料信息,
你的工作是写一个程序读出这些文件然后对每个收容动物进行适当的处理(appropriate
processing) 。
完成这个程序一个合理的方法是定义一个抽象类,ALA("Adorable Little Animal"),
然后为小狗和小猫建立派生类。 一个虚拟函数processAdoption分别对各个种类的动物进行
处理:
public:
virtual void processAdoption() = 0;
...
};
class Puppy: public ALA {
public:
virtual void processAdoption();
...
};
class Kitten: public ALA {
public:
virtual void processAdoption();
...
};
你需要一个函数从文件中读去信息,然后根据文件中的信息产生一个puppy(小狗)对
象或者kitten(小猫)对象。这个工作非常适合于虚拟构造器(virtual constructor) ,在
条款M25详细描述了这种函数。为了完成我们的目标,我们这样声明函数:
// 从s中读去动物信息, 然后返回一个指针
// 指向新建立的某种类型对象
ALA * readALA(istream& s);
你的程序的关键部分就是这个函数,如下所示:
void processAdoptions(istream& dataSource)
{
while (dataSource) { // 还有数据时,继续循环
ALA *pa = readALA(dataSource); //得到下一个动物
pa->processAdoption(); //处理收容动物
delete pa; //删除readALA返回的对象
}
}
现在考虑一下,如果pa->processAdoption抛出了一个异常,将会发生什么?
processAdoptions没有捕获异常, 所以异常将传递给processAdoptions的调用者。 传递中,
processAdoptions函数中的调用pa->processAdoption语句后的所有语句都被跳过,这就
是说pa没有被删除。结果,任何时候pa->processAdoption抛出一个异常都会导致
processAdoptions内存泄漏。
堵塞泄漏很容易 :
void processAdoptions(istream& dataSource)
{
while (dataSource) {
ALA *pa = readALA(dataSource);
try {
pa->processAdoption();
}
catch (...) { // 捕获所有异常
delete pa; // 避免内存泄漏
// 当异常抛出时
throw; // 传送异常给调用者
}
delete pa; // 避免资源泄漏
} // 当没有异常抛出时
}
在这种情况下,必须写两个delete代码。象其它重复代码一样, 这种代码写起来令人心烦又难于维护, 而且它看上去好像存在着问题
使用智能指针改进版:
void processAdoptions(istream& dataSource)
{
while (dataSource) {
auto_ptr<ALA> pa(readALA(dataSource));
pa->processAdoption();
}
}
这个版本的processAdoptions在两个方面区别于原来的processAdoptions函数。 第一,
pa被声明为一个auto_ptr<ALA>对象,而不是一个raw ALA*指针。第二,在循环的结尾没有delete语句。其余部分都一样,因为除了析构的方式,auto_ptr对象的行为就象一个
普通的指针。是不是很容易。
隐藏在auto_ptr后的思想是:用一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源,这种思想不只是可以运用在指针上,还能用在其它资源的分配和释放上。
想一下这样一个在GUI程序中的函数,它需要建立一个window来显式一些信息:
// 这个函数会发生资源泄漏,如果一个异常抛出
void displayInfo(const Information& info)
{
WINDOW_HANDLE w(createWindow());
在w对应的window中显式信息
destroyWindow(w);
}
很多window系统有C-like接口,使用象like createWindow 和destroyWindow函数
来获取和释放window资源。如果在w对应的window中显示信息时,一个异常被抛出,w所
对应的window将被丢失,就象其它动态分配的资源一样。
解决方法与前面所述的一样,建立一个类,让它的构造函数与析构函数来获取和释放资
源:
//一个类,获取和释放一个window 句柄
class WindowHandle {
public:
WindowHandle(WINDOW_HANDLE handle): w(handle) {}
~WindowHandle() { destroyWindow(w); }
operator WINDOW_HANDLE() { return w; } // see below
private:
WINDOW_HANDLE w;
// 下面的函数被声明为私有,防止建立多个WINDOW_HANDLE拷贝
//有关一个更灵活的方法的讨论请参见条款M28。
WindowHandle(const WindowHandle&);
WindowHandle& operator=(const WindowHandle&);
};
这看上去有些象auto_ptr,只是赋值操作与拷贝构造被显式地禁止,有一个隐含的转换操作能把 WindowHandle转换为WINDOW_HANDLE。这个能力对于使用WindowHandle对象非常重要,因为这意味着你能在任何地方象使用raw WINDOW_HANDLE一样来使用WindowHandle。
通过给出的WindowHandle类,我们能够重写displayInfo函数,如下所示:
// 如果一个异常被抛出,这个函数能避免资源泄漏
void displayInfo(const Information& info)
{
WindowHandle w(createWindow());
在w对应的window中显式信息;
}
即使一个异常在displayInfo内被抛出,被createWindow 建立的window也能被释放。
资源应该被封装在一个对象里,遵循这个规则,你通常就能避免在存在异常环境里发生
资源泄漏。