7 More Effective C++—条款10(构造函数内阻止内存泄漏)

1 提出问题

上一篇文章中,我们讨论了如下情况,当函数doSomething()被调用时,heap中资源无法被释放,导致内存泄漏问题发生。

void function() {
	MyObject *object = new MyObject;
	object->doSomething();
	delete object;
}

本篇中,我们将讨论这样一种情况:当类中需要“包含多个heap对象“,但是在构造函数中却出现异常的情况下,如何释放掉已经创建的heap对象。

考虑如下情景。电话簿中需要包含如下信息:姓名,号码,头像,一段特定响铃音乐。可以定义如下类。

class BookEntry {
	public:
		BookEntry(const string &name, const string& address,
						const string& image, const string& audio);
		~BookEntry();
	private:
		string m_name, m_address;
		std::list<PhoneNumber> m_number;
		Image* m_photo;
		Audio *m_audio;
}
BookEntry::BookEntry(const string &name, const string& address,
						const string& image, const string& audio) {
	if (image != "") {
		m_image = new Image(image);
	}
	if (audio != "") {
		m_audio = new Audio(audio);
	}
}
BookEntry::~BookEntry() {
	delete m_image; // C++允许空指针被delete,不会抛出异常
	delete m_audio; 
}

上面代码中,若在执行new Audio(audio)语句中出现异常,由于类并没有创建成功,析构函数不会被调用,因此m_image指向的内存就会出现内存泄漏。所以,下面的代码不会执行。

void testBookEntry() {
	BookEntry entry = 0;
	try {
		// 构造函数抛出异常,m_image指向heap内存可能会泄漏
		// entry可能为null指针
		entry = new BookEntry("Danny", "Beijing", "photo.jpg", "voic.mp3"); 
		...
	} catch (...) {
		delete entry;// 不会抛出异常,但是m_image指向内存依然会泄漏
		throw;
	}
	delete entry; // 不会抛出异常,但是m_image指向内存依然会泄漏

2 解决步骤

首先,对每个在heap中的内存资源进行记录,会增加维护成本和开销,导致程序效率低下,臃肿。比较好的解决办法时,当问题出现时,集中处理,然后再进一步抛出异常。

BookEntry::BookEntry(const string &name, const string& address,
						const string& image, const string& audio) {
	try {
		if (image != "") {
			m_image = new Image(image);
		}
		if (audio != "") {
			m_audio = new Audio(audio);
		}
	} catch (...) { // 析构函数和本动作相同,因此可包装到一个函数中:cleanUp()
		cleanUp();
	}
}
BookEntry::~BookEntry() {
	cleanUp();
}
void BookEntry::cleanUp() {
	delete m_image;
	delete m_audio;
}

3 初始化列表中的内存泄漏

上面对m_image和m_audio的初始化,放在了构造函数中。

但是,如果m_image变量声明为const类型,就不得不将其初始化动作放到初始化列表中,我们就无法使用try…catch来捕捉异常,依然会造成m_image内存资源泄漏。如下面所示。

Image * const m_image; // 指针的指向不能修改
BookEntry::BookEntry(const string &name, const string& address,
						const string& image, const string& audio) 
		: m_name(name), m_address(address),
		m_image(image == "" ? 0 : new Image(image)),
		m_audio(audio == "" ? 0 : new Audio(audio)) { // new Audio抛出异常,会造成m_image内存泄漏
}

4 解决办法

1 办法一

一种办法是,针对每个变量定义一个构建函数,在heap中创建变量的工作放到这个构建函数中,并返回创建好的指针。如下代码所示。

BookEntry::BookEntry(const string &name, const string& address,
						const string& image, const string& audio) 
		: m_name(name), m_address(address),
		m_image(createImage(image)),
		m_audio(createAudio(audio)) {
}
Image* createImage(const std::string& image) {
	Image *tmpImage = 0;
	try {
		 // doSomething
	} catch () {
	}
	return tmpImage;
}

2 办法二

就像上一篇内容建议的,我们直接将指针包裹到auto_ptr中,利用“作用域”和“生命周期”来控制具体的行为。

std::auto_ptr<Image> m_image;
std::auto_ptr<Audio> m_audio;

当构造m_audio时,若出现任何异常,就像m_name, m_address一样,m_image也会被自动销毁,保证了内存不会被泄漏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值