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也会被自动销毁,保证了内存不会被泄漏。