栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

More Effective C++ 条款10:在构造内阻止资源泄漏

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

More Effective C++ 条款10:在构造内阻止资源泄漏

当在构造函数中抛出异常的时候,由于对象尚未构造完全,因此并不会调用其析构函数,问题是此时如果对象已经被部分构造,那么我们应当保证被部分构造的内容适当地析构。

考虑这样一个类

class A{
public:
    A(const int& number,const string&a,const string& b):num(number),ptr_a(0),ptr_b(0){
        if (a != "")
            ptr_a = new string(a);
        if (b != "")
        ptr_b = new string(b);
    }
    ~A(){ delete ptr_a; delete ptr_b; }
private:
    int num;
    string* ptr_a;
    string* ptr_b;
};

如果A的构造函数中在为ptr_b所指向的对象分配内存的时候抛出一个异常,编译器不会调用A的析构函数,那么释放ptr_a所指向的内存的任务就落到了程序员身上,可以将构造函数改写为以下形式来适当的释放ptr_a所指向的内存:

class A{
public:
    A(const int&  number,const string&a,const string& b):num(number),ptr_a(0),ptr_b(0){
        try{
            if (a != "")
                ptr_a = new string(a);
            if (b != "")
                ptr_b = new string(b);
        }
        catch(...){
            delete ptr_a; //可以直接delete,因为delete一个空指针不会有影响
            delete ptr_b; //同上
            throw;
        }
    }
    ~A(){ delete ptr_a; delete ptr_b; }
private:
    int num;
    string* ptr_a;
    string* ptr_b;
};

不需要担心A类的non-pointer data members(指的是num),data member会在A的构造函数内代码执行之前就被初始化好(因为使用了初始化列表),一旦A类型对象被销毁,这些data member会像已经被构造好的对象一样被自动销毁。

你可以发现catch语句块内的动作与A类的析构函数动作相同, 因此可以把他们放入一个辅助函数内,如下:

class A{
public:
    A(const int&  number,const string&a,const string& b):num(number),ptr_a(0),ptr_b(0){
        try{
            if (a != "")
                ptr_a = new string(a);
            if (b != "")
                ptr_b = new string(b);
        }
        catch(...){
            cleanup();
            throw;
        }
    }
    ~A(){ cleanup(); }
    void cleanup(){
        delete ptr_a;
        delete ptr_b;
    }
private:
    int num;
    string* ptr_a;
    string* ptr_b;
};

以上代码可以比较好的解决构造函数内抛出异常时对象的析构问题,但是如果A类的ptr_a和ptr_b被声明为const,则需要另外一番考虑(此时必须在初始化列表内初始化ptr_a和ptr_b),A的构造函数可以像以下这样定义:

class A{
public:
    A(const int&  number,const string&a,const string& b)
        :num(number),
        ptr_a(iniA()),
        ptr_b(iniB()){

    }
    ~A(){ cleanup(); }
    void cleanup(){
        delete ptr_a;
        delete ptr_b;
    }
private:
    string* iniA();
    string* iniB();
    
    int num;
    string* const  ptr_a;
    string* const  ptr_b;
};

string* A::iniA()
{
    return new string("A");
}
string* A::iniB()
{
    try{
        return new string("B");
    }
    catch(...){
        delete ptr_a;
    }
}

我们可以继续优化,将ptr_a和ptr_b所指对象视为资源,交给局部对象来管理。

class A{
public:
    A(const int&  number, const string&a, const string& b) :num(number), ptr_a(a != "" ? new string(a) : 0), ptr_b(b != "" ? new string(b) : 0){
    }
private:
    int num;
    const auto_ptr ptr_a;
    const auto_ptr ptr_b;
};
//在此设计中,如果ptr_b在构造过程中有任何异常,由于ptr_a已经是构造好的对象,在运行其析构函数时,所指内存会被自动释放,此外,由于ptr_a和ptr_b如今都是对象,当当其”宿主“(A类对象)被销毁时,它们说所指内存也会被自动释放

总结: “C++ 只会析构已完成的对象”,“面对未完成的对象,C++ 拒绝调用其析构函数”,因为对于一个尚未构造完成的对象,构造函数不知道对象已经被构造到何种程度,也就无法析构。当然,并非不能采取某种机制使对象的数据成员附带某种指示,“指示constructor进行到何种程度,那么destructor就可以检查这些数据并(或许能够)理解应该如何应对。但这种机制无疑会降低constructor的效率,处于效率与程序行为的取舍,C++ 并没有使用这种机制。所以说,”C++ 不自动清理那些’构造期间跑出exception‘的对象“。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/768119.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号