第三章,异常

条款9 利用destructors避免资源泄露

为什么需要使用Exception?

如果函数只是返回错误码或发出一个异常信号,无法确保函数调用者会检查返回的错误码,使得程序保留错误隐患继续运行下去;但是如果函数以抛出exception的方式发出异常信号,而该exception未被捕捉,程序的执行便会立即中止。
本章的条款并不能架构完全的”exception-safe软件”设计指南,但只要留意这些指引就可以改善软件的正确性、稳健性、效率,并且可以避开很多因exception产生的问题。

利用destructors避免资源泄露

小动物收养保护中心的类的设计例子

class ALA
{
public:
virtual void processAdoption() = 0;
...
};

class Puppy: public ALA{
public:
virtual void processAdoption();
...
};

class Kitten: public ALA{
public:
virtual void processAdoption();
...
};

//从s读取动物信息,返回指针,指向新分配的对象(Puppy或者Kitten)
ALA * readALA(istream& s)
{
//简略伪代码:
if(isPuppyInfo)
return (new Puppy());
else if(Kitten)
return (new Kitten());
}

//程序核心函数:
void processAdoptions(istream& dataSource)
{
while(dataSource) // 遍历动物信息文档
{
ALA *pa = readALA(dataSource); // 取出一只动物
pa->processAdoption(); // 处理对应的收养事宜
delete pa; // 删除readALA返回的对象
}
}

可以看到, delete pa;是防止资源泄漏的必要代码;现在考虑一种情况:如果pa->processAdoption()抛出exception,会发生什么?

由于processAdoptions函数内部没有try,catch,因此没有捕捉到异常,该异常会传播到processAdoptions的调用端。当pa->processAdoption()抛出了一个未被捕获的异常时,那么紧跟在它后面的 delete pa; 永远不会被执行,造成资源泄露!

为避免以上的情况发生,可以修改为以下代码:

//程序核心函数:
void processAdoptions(istream& dataSource)
{
while(dataSource) // 遍历动物信息文档
{
ALA *pa = readALA(dataSource); // 取出一只动物

try{
pa->processAdoption(); // 处理对应的收养事宜
}
catch(...){ //catch(...)万能捕获
delete pa;
throw;
}

delete pa; // 删除readALA返回的对象
}
}

但是上面的代码仍然有让人不满意的地方,一是try catch语句“分裂”了程序语句,难看;再者是delete pa重复编写,这些都是提升了程序代码的维护难度。
那是否有更优雅的设计呢?
当然是有的,那就是将delete pa这样的清理操作移到processAdoptions中使用的某个局部对象的析构函数destructor内即可(栈上局部对象要销毁时自动调用析构函数完成清理工作)。下面详细说一下怎么做:

可以使用auto_ptr代替直接使用pa指针来实现这个思路,auto_ptr是C++标准库提供的一个class template;其构造函数要求得到一个指向heap object的指针,其析构函数会将该heap object删除。(嗯,像是一个指针保姆,管家?) 只涉及以上两点的话,auto_ptr看起来就像下面的代码:

template<class T>
class auto_ptr{
public:
auto_ptr(T *p = 0): ptr(p) {} //存储对象
~auto_ptr() { delete ptr; } //删除对象
private:
T *ptr; //原始指针,指向原对象
}

以auto_ptr对象取代原始指针后:

void processAdoptions(istream& dataSource)
{
while(dataSource){
auto_ptr<ALA> pa(readALA(dataSource));
pa->processAdoption();
}
}

// 哇哦! 优雅👍

这个例子背后的观念尤为重要: 以一个对象存放“必须自动释放的资源” ,并依赖该对象的析构函数释放!