C++在构造函数中防止资源泄露(9)---《More Effective C++》

5 篇文章 0 订阅

参考博客:http://blog.csdn.net/lisonglisonglisong/article/details/44276343

C++的构造函数不仅包括申请内存空间的过程,同时也包括了调用构造函数的过程。试想一下,如果在构造函数使用过程中,抛出异常,会发生什么情况呢?这篇博客我们主要围绕构造函数中的异常进行分析:

1、如果构造函数抛出异常将导致析构函数不被执行:

一个异常被抛出,可能是因为operator new不能给AudioClip分配足够内存,也可能是Audio构造函数自己抛出一个异常,无论什么原因,这都会导致对象没有被创建成功,因此析构函数不会被调用,因为C++仅能删除完全构造的对象。那么我们通过删除指针对象的方式呢?

class BookEntry{
public:
    BookEntry(const string&name,const string&address="",const string& imageFileName="",const string& audioClipFileName="");
    ~BookEntry();
    ...
private:
    string theName;
    string theAddress;
    Image* theImage;
    AudioClip* theAudioClip;
};
void testBookEntryClass(){
    BookEntry* pb=0;
    try{
        pb=new BookEntry("hello","world");
        ...
    }catch(...){
        delete pb;
        throw;
    }
}

我们以为这样就可以解决问题了?那么,问题有木有被解决呢?答案是否定的。
因为new操作没有成功完成,程序不会对pb进行赋值,即现在pb为空指针,delete pb即删除一个空指针,没有释放我们在堆中的内存等,所以这样没有任何用!!!还是会存在内存泄露的问题!

2、构造函数出现异常可能导致内存泄露:

我们先来分析一下如下代码:

#include <iostream>
#include <string>
#include <csetjmp>
using namespace std;

class MyInt{
private:
    int i;
public:
    MyInt(){

    }
    MyInt(int i) :i(i){
        cout << "MyInt的构造函数开始执行。。。" << endl;
    }
    ~MyInt(){
        cout << "对MyInt进行析构" << endl;
    }
};
class MyString{
public:
    MyString(){
        cout << "MyString进行构造。。。" << endl;
        mi = new MyInt(10);
    }

    ~MyString(){
        delete mi;
        cout << "MyString进行析构。。。" << endl;
    }
private:
    MyInt *mi;
};
int main(){
    MyString *ms = 0;
    try{
         ms= new MyString();
    }
    catch (...){
        delete ms;
    }
    delete ms;
    return 0;
}

乍看这段代码没有什么问题,但是如果当MyString中的MyInt* mi调用构造函数的过程中出现异常,此时由于ms对象的构造函数没有执行成功,所以编译器拒绝为这种不完全的对象调用析构函数,即使调用了析构函数delete ms,由于ms此时为空指针,也没有任何用,所以,这将导致ms的析构函数无法执行,其中MyInt* mi在堆中的内存肯定也无法得到释放。。。
PS:
我们无需为对象中的非指针成员担心,因为非指针成员在异常抛出前面已经构造完全,所以可以自动逆序析构。

3、解决异常中内存泄露的办法:在构造函数中进行try、catch分析:

因为对象在构造函数抛出异常后不负责清除对象那个,所以我们必须重新设计我们的构造函数使得它们可以自己清除。常用办法是捕获所有异常,然后执行一些清除代码,最后在重新抛出异常让其继续传递:

BookEntry::BookEntry(const string& name,const string& address,const string& imgFileName,const string& audioClipFileName):theName(name),theAddress(address),theImage(0),theAudioClip(0){
    try{
        if(imgFileName!=""){
            theImage=new Image(imageFileName);
        }
        if(audioClipFileName!=""){
            theAudioClip=new AudioClipe(audioClipFileName);
        }
    }catch(...){
        delete theImage;
        delete theAudioClip;
        throw;
    }
}

我们在这儿针对构造函数一旦出现异常的情况,使用构造函数自己捕获异常并在继续传递异常之前完成必要的清除操作。
上述代码可以进行进一步优化处理:

BookEntry::BookEntry(const string& name,const string& address,const string& imgFileName,const string& audioClipFileName):theName(name),theAddress(address),theImage(0),theAudioClip(0){
    try{
        if(imgFileName!=""){
            theImage=new Image(imageFileName);
        }
        if(audioClipFileName!=""){
            theAudioClip=new AudioClipe(audioClipFileName);
        }
    }catch(...){
        delete theImage;
        delete theAudioClip;
        throw;
    }
}

可以对上面代码进行多个版本的优化,最后我们选择了最靠谱的一种:

class BookEntry{
public:
    ...
private:
    ...
    const auto_ptr<Image> *theImage;
    const auto_ptr<AudioClip> theAudioClip;
};
BookEntry::BookEntry(const string&name,const string& address,const string& imageFileName,const string& audioClipFileName):theName(name),theAddress(address),theImage(imageFileName!=""?new Image(imageFileName):0),theAudioClip(audioClipFileName!="")?new AudioClip(audioClipFileName):0){}

这里因为我们采用了auto_ptr所以,我们可以使用表达式赋值,注意这里没有带try、catch语句块哟!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值