6 More Effective C++—条款9(局部变量的destructor防止内存泄漏)

0 生活鸡汤

偶然看到一篇文章,每天前进一点点,积累下来,人生就能有所改变。已经有一段时间没有更新这个系列,今天争取再往前走一点点。

1 提出问题

宠物医院提供收养服务,其中,主要收养对象是小狗(Dog)小猫(Cat)。收养需要走一定流程,具体流程我们不必关心。上面的情景可用下面代码描述。

class Animal {
	public:
		virtual void processAdoption() = 0;
}
class Cat : public Animal {
	public:
		virtual void processAdoption();
}
class Dog : public Animal {
	public:
		virtual void processAdoption();
}

void batchAdoption(istream& dataSource) {
	while (dataSource) {
		Animal *animal = readAnimal(dataSource);
		animal->processAdoption();
		delete animal;
	}
}

但是,如果上面的batchAdoption()方法中,readAnimal(), processAdoption(), 都可能抛出异常, 程序中断,从而导致delete animal无法执行,内存泄漏发生。

2 解决途径

由于“防止内存泄漏”时本书的一个重要主题,为了一步步揭示思维过程,和书中内容保持一致,下面将给出逐步优化过程。

1 利用“异常捕获”

考虑到上面readAnimal()和processAdoption()都有可能出现异常,因此可以将两个语句放入try中。

这样的代码比较冗余,因此我们是否可以进一步将delete操作集中到一处进行处理?

void batchAdoption(istream& dataSource) {
	while (dataSource) {
		Animal *animal = readAnimal(dataSource); // 不能放入try中,否则animal对外部不可见
		try {
			animal->processAdoption();
		} catch (...) {
			delete animal;
			throw;
		}
		delete animal;
	}
}

2 将指针用对象抱起来

我们可以将readAnimal()返回的指针,作为构造参数,放入一个类对象中;将delete animal的动作,放到类对象的析构函数中。此时,一旦退出类对象所在作用域,其析构函数被调用,那么delete animal就会被执行。

STL提供了auto_ptr模板类来实现上面的设计。其可能的实现如下。

template <class T>
class auto_ptr {
	public:
		auto_ptr(T *p = 0) : m_ptr(p) {}
		~auto_ptr() { delete m_ptr;}
	private:
		T *m_ptr;
};

这样,上面的函数可以实现为下面的代码。

void batchAdoption(istream& dataSource) {
	while (dataSource) {
		auto_ptr animal(readAnimal(dataSource));
		animal->processAdoption();
		// 无需调用语句delete animal,出了作用域即调用析构函数
	}
}

3 进一步应用

至此,我们的核心观念已经提出:

1,利用“作用域”和“生存周期”来控制heap中对象的生存周期。
2,进一步,利用“作用域”和“生存周期”,来控制函数局部内的行为。

根据上面的道理,进一步应用到现实场景。在窗口显示信息的过程中,出现异常,则窗口指针w将无法被销毁。因此,我们可以用类对象将w封装,从而保证无论什么情况下,w总能被销毁。

class WindowHandle {
	public:
		WindowHandle(Window_Handle handle) : w(handle){}
		~WindowHandle() {destroyWindow(w);}
		operator Window_Handle() { return w;} //隐式类型转换函数
	private:
		Window_Handle w;
		WindowHandle(const WindowHandle&); // 屏蔽复制构造函数
		WindowHandle& operator=(const WindowHandle& ); // 屏蔽复制构造函数

4 提出新问题

后面条款10和条款11将分别讨论如下两个问题:

1,当初始化包装heap指针的时候,抛出异常。
2,当析构包装heap指针的时候,抛出异常。

猜你喜欢

转载自blog.csdn.net/zhizifengxiang/article/details/83212104