• 关于构造函数和异常的分析


    如果构造函数内发生异常,已经分配的资源是不会自动释放的,比如

    class B{
    public:
        B(){
            printf("into B constructor
    ");
        }    
        ~B(){
            printf("into B destructor
    ");
        }
    };
    
    class C{
    public:
        C(){
            printf("into C constructor
    ");
            throw std::runtime_error(" exception from C constructor");
        }    
        ~C(){
            printf("into C destructor
    ");
            }
    };
    class A{
    public:
        A(){
            printf("into A constructor 
    ");
        }
        ~A(){
            printf("into A destructor 
    ");
        }
    };
    
    class D:A{
    public:
        D():A(), b(NULL),c(NULL) {
            printf("into D constructor
    ");
            b = new B();
            c = new C();
        }
        ~D(){
            printf("into D destructor
    ");
            delete b;
            delete c;
        }
    private:
        B *b;
        C *c;
    };
    
    int main(int argc, char **argv){
        D d;
        return 0;
    }

    运行结果如下:

    into A constructor

    into D constructor

    into B constructor

    into C constructor

    terminate called after throwing an instance of 'std::runtime_error'

    what(): exception from C constructor

    对象c在构造过程中抛出异常,指针b指向的内存空间不会被释放。

    如何释放b的内存呢?首先我们可以看出c++是不会调用析构函数的,因为析构函数不知道对象已经构造了多少,哪些资源该释放,哪些不该释放,当然编译器可以记录这些内容,但是会严重影响效率。另外在语义上,c++认为,对象的生命周期是构造函数正常结束至析构函数结束之间,构造不完全的对象是一个没有生命的东西,是不存在的,你不能对一个不存在的对象调用析构函数。编译器默认会做的只是释放对象d的内存空间。对象b指向的堆内存可以通过使用异常显示释放

    D():A(), b(NULL), c(NULL){
            printf("into D constructor
    ");
            try{
                b = new B();
                c = new C();
            }catch(std::runtime_error &e){
                printf("into D constructor catch 
    ");
                delete b; b=NULL;
                delete c; c=NULL;
           }
    }

    运行结果如下:

    into A constructor

    into D constructor

    into B constructor

    into C constructor

    into D constructor catch

    into B destructor

    into D destructor

    into A destructor

    b被正常释放了,我们再做一下改变,将b和c的构造放到初始化列表中做,将D的构造函数改成下面这样,

    D::D() : A(),b(new B()),c(new C())
       {
       }

    我们继续使用异常,会不会有效?

    D() try:A(), b(new B()), c(new C()) {
            printf("into D constructor
    ");
    }catch(std::runtime_error &e){
            printf("in D constructor catch: %s 
    ", e.what());
            cleanup();
    }

    看上去very nice啊,跑一下试试,

    into A constructor

    into B constructor

    into C constructor

    into A destructor

    in D constructor catch: exception from C constructor

    into B destructor

    into C destructor

    *** glibc detected *** ./a.out: free(): invalid pointer: ***

    指针错误!同时我们可以发现在进入catch语句前基类A执行了析构函数,这说明到达catch语句时,已经跳出了构造函数的范围,D和A的成员数据都已经是不可访问的了。

    C++为什么要这样做呢,为什么构造函数体外的catch语句中无法访问数据成员?

    首先,无法知道b和c是否已经初始化了,删除未初始化指针是非法的。

    其次,我们假设可以知道b初始化了,c没有初始化,我们要删除b,如果catch中可以访问b和c的话(我们做个假设),改变B的类型,使B包含一个A*成员数据,考虑下这种情况,

    D() try:A(), b(new B(static_cast<A*>(this))), c(new C()) {
             printf("into D constructor
    ");
    }catch(std::runtime_error &e){
             printf("in D constructor catch: %s 
    ", e.what());
             cleanup();
    }

    A已经析构了,b的数据成员A已经不存在,这是很危险的。

    c++认为,不管是基类还是数据成员构造出现失败,那么整个对象构造都是失败的,不存在半成品。构造函数块外的catch语句(上面这种)即使没有显示地重新抛出异常,c++也会自动抛出,一直到最下层派生类对象构造处停住。比如以上例子,catch没有显示throw,在main函数里:

    try{    
            D d;
    }catch (std::runtime_error &e){
            printf("int main: %s
    ", e.what());
    }

    仍然会捕捉到C构造函数抛出的runtime_error异常。

    综上,可以总结以下规则

    1. 构造函数体外的try-catch语句只有一个用途——转换捕捉到的异常对象

    2. 必须在构造函数体内分配资源,不要在初始化列表中

    3. 一定要用try-catch语句管理资源的话,在构造函数体内。

    4. 使用RAII方式管理资源,这会少死亡很多脑细胞,像这样子

    class D{
        auto_ptr<B> b;
        auto_ptr<C> c;
    }
    D::D() : A( ),b(new B()), c(new C())
    {
    }

     欢迎讨论

    参考

    http://www.gotw.ca/gotw/066.htm

    《more effective c++》


    作者:coderkian
    出处:http://www.cnblogs.com/coderkian/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    lucene学习-创建索引
    ExtJs学习-搭建开发环境
    Struts2上传文件(1)
    使用USBWriter做U盘启动盘后U盘在盘中不显示的解决办法(轉載)
    家里旧电脑装了centos7實踐思路
    win7/win10下装centos7双系统(转载)
    美区google play礼品卡,如何正确充值到美区google play余额,并能购买游戏道具
    excel 2016 打开UTF-8编码CSV文件乱码的问题UTF-8编码CSV文件乱码的问题
    python3 writerow CSV文件多一个空行
    python3 UnicodeEncodeError: 'gbk' codec can't encode character 'xa0' in position 4400: illegal multibyte sequence
  • 原文地址:https://www.cnblogs.com/coderkian/p/3485581.html
Copyright © 2020-2023  润新知