现在的位置: 首页 > 综合 > 正文

more-effective-c++ 序列2 异常(第10节,在构造函数中抛出异常导致资源泄露)的测试示例

2013年04月20日 ⁄ 综合 ⁄ 共 2749字 ⁄ 字号 评论关闭

声明:

本例子描述了在构造函数抛出异常会导致资源泄露的过程。通过本例子,希望我们在编写自己的构造函数的时候,要小心一些可能的异常。如果代码对异常考虑的不够,那么出现异常很有可能导致我们的资源发生泄露。

例子说明一切:

主要是BookEntry类的构造函数中AudioClip成员初始化的时候出现了异常,这直接导致已经构造完成的Image对象的堆内存没有被正确的释放。仔细看一下代码,便可知。下一节,将给出通过智能指针对这个问题有一个比较完美的解决的例子。

#include <iostream>
#include <time.h>
#include <memory>
using namespace std;

/*
作者:lpstudy
日期:2013-3-16
内容:more effective C++异常一章第10节,在构造函数中防止资源泄露,这个是资源泄露的例子,可以通过打印的屏幕看到new Image的资源确实泄露了。
*/
#define TRACE_FUCTION_AND_LINE(fmt, ...) printf("[%30s:%4d] "fmt"\n",__FUNCTION__, __LINE__, ##__VA_ARGS__)


class Image
{
public:
	Image(const string& strImgName)
		:m_strImgName(strImgName)
	{
		TRACE_FUCTION_AND_LINE();
	}
	~Image(){TRACE_FUCTION_AND_LINE();}
private:
	const string& m_strImgName;
};


/*
AudioClip类是在image类构造完成之后才进行构造,下面的AudioClip抛出异常可用来观察image的析构函数实际上由于异常并没有被调用
导致image内存泄露。
*/
class AudioClip
{
public:
	AudioClip(const string& strAudioName)
		:m_strAudioName(strAudioName)
	{
		TRACE_FUCTION_AND_LINE();
		throw 1;//BookEntry抛出异常,导致Image析构没有被调用
	}
	~AudioClip(){TRACE_FUCTION_AND_LINE();}
private:
	const string& m_strAudioName;
};

/*
BookEntry构造函数负责初始化Image和AudioClip,这样如果Image和AudioClip的构造函数
出现异常的话,BookEntry构造函数会立刻返回,这样构造的对象就不是一个完整的对象。
*/
class BookEntry
{
public:
	BookEntry(const string& name, 
			  const string& address = "",
			  const string& imageFileName = "",
			  const string& audioclipName = "")
	: m_strName(name),
	  m_strAddress(address),
	  m_pImage(0),
	  m_pAudioClip(0)
	{
		TRACE_FUCTION_AND_LINE();
		if(!imageFileName.empty())
		{
			m_pImage = new Image(imageFileName);
		}
		if(!audioclipName.empty())
		{
			m_pAudioClip = new AudioClip(audioclipName);
		}
	}
	~BookEntry()
	{
		delete m_pImage;
		delete m_pAudioClip;
		TRACE_FUCTION_AND_LINE();
	}
	void Log () {TRACE_FUCTION_AND_LINE("My Log------"); throw 1;}
 
private:
	string m_strName;
	string m_strAddress;
	Image* m_pImage;
	AudioClip* m_pAudioClip;
};

/*
测试构造函数异常的时候,栈内存的析构函数不会被调用
导致Image内存泄露
*/
void TestStackMemory()
{
	try{
		TRACE_FUCTION_AND_LINE("");
		BookEntry bookEntry("lpstudy", "beijing", "imageFileName", "audioClipName");
	}
	catch(int e)
	{
		TRACE_FUCTION_AND_LINE("exception int = %d", e);
	}
}
/*
测试堆内存的时候,构造函数异常的时候
可以看出这个时候pBookEntry还没有完全构造出来,返回的指针实际上是NULL
导致Image内存泄露
*/
void TestHeapMemory()
{
	BookEntry *pBookEntry = 0;
	try{
		TRACE_FUCTION_AND_LINE("");
		pBookEntry = new BookEntry("lpstudy", "beijing", "imageFileName", "audioClipName");	
	}
	catch(int e)
	{
		TRACE_FUCTION_AND_LINE("exception int = %d", e);
		TRACE_FUCTION_AND_LINE("pBookEntry = %08p", pBookEntry);
		delete pBookEntry;//实际上delete null, 没有任何意义
	}
}
int main()
{
	TRACE_FUCTION_AND_LINE("Trace BookEntry.......");
	TestStackMemory();
	TestHeapMemory();

	return 0;
}
/*
以上代码清楚的反映了由于BookEntry构造函数异常导致已经分配的堆内存Image对象的析构函数不会被执行(由于BookEntry的析构函数没有执行),
导致Image内存泄露。
解决方案:
1,构造函数的函数体中加入try,catch,在catch的时候调用cleanup函数。
2,方案1对如果指针是const pointer,不行。因为const pointer必须在成员初始化列表中被初始化,这个时候需要引用InitImage,InitAudioClip方法,
这两个方法中负责try,catch,并返回构造的image和audioclip对象指针,送给成员初始化列表初始化,这样导致成员函数增多,导致代码膨胀,维护很混乱
3,方案3采用智能指针auto_ptr,不管const还是non-const指针都可以有同样的处理结果,代码简单清晰,这个将代码实现,毕竟有很大的实用价值。
*/

抱歉!评论已关闭.