27.能否在构造函数中抛出异常?析构函数呢?

首先,我们要明确一点!一个函数执行的过程中,如果抛出异常,会导致函数提前终止!

在C++构造函数中,既需要分配内存,又需要抛出异常时要特别注意防止内存泄露的情况发生。因为在构造函数中抛出异常,在概念上将被视为该对象没有被成功构造,因此当前对象的析构函数就不会被调用。同时,由于构造函数本身也是一个函数,在函数体内抛出异常将导致当前函数运行结束,并释放已经构造的成员对象,包括其基类的成员,即执行直接基类和成员对象的析构函数。所以在构造函数中尽量不要抛出异常,但可以抛出异常(后面给出方法),要注意内存泄漏的问题!

举个栗子:

#include <iostream>
using namespace std;

class C
{
    int m;
public:
    C(){cout<<"in C constructor"<<endl;}
    ~C(){cout<<"in C destructor"<<endl;}
};

class A
{
public:
    A(){cout<<"in A constructor"<<endl;}
    ~A(){cout<<"in A destructor"<<endl;}
};

class B:public A
{
public:
    C c;
    char* resource;

    B()
    {
        resource=new char[100];
        cout<<"in B constructor"<<endl;
        throw -1;
    }
    ~B()
    {
        cout<<"in B destructor"<<endl;
        delete[]  resource;
    }
};

int main()
{
    try
    {
        B b;
    }
    catch(int)
    {
        cout<<"catched"<<endl;
    }
}

输出:

in A constructor
in C constructor
in B constructor
in C destructor
in A destructor
catched

从输出结果可以看出,在构造函数中抛出异常,当前对象的析构函数不会被调用,在构造函数中分配了内存,造成内存泄露,所以要格外注意。

此外,在构造对象b的时候,先要执行其直接基类A的构造函数,再执行其成员对象c的构造函数,然后再进入类B的构造函数。由于在类B的构造函数中抛出了异常,而此异常并未在构造函数中被捕捉,所以导致类B的构造函数执行中断,对象b并未构造完成。在类B的构造函数“回滚”的过程中,c的析构函数和类A的析构函数相继被调用。最后,由于b并没有被成功构造,所以main()函数结束时,并不会调用b的析构函数,也就很容易造成内存泄露。

构造函数异常,可以总结如下:   

1.C++中通知对象构造失败的唯一方法那就是在构造函数中抛出异常;   

2.构造函数抛出异常时,析构函数将不会被执行;   

3.抛出异常时,其子对象将被逆序析构。


使用智能指针管理内存资源

使用RAII(Resource Acquisition is Initialization)技术可以避免内存泄漏。RAII即资源获取即初始化,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。智能指针是RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。

因此,当构造函数不得已抛出异常时,可以利用“智能指针”unique_ptr来防止内存泄露。参考如下程序

#include <iostream>
using namespace std;

class A
{
public:
    A() { cout << "in A constructor" << endl; }
    ~A() { cout << "in A destructor" << endl; }
};

class B
{
public:
    unique_ptr<A> pA;
    B():pA(new A)
    {
        cout << "in B constructor" << endl;
        throw - 1;
    }
    ~B()
    {
        cout << "in B destructor" << endl;
    }
};

int main()
{
    try
    {
        B b;
    }
    catch (int)
    {
        cout << "catched" << endl;
    }
}

运行结果:

in A constructor
in B constructor
in A destructor
catched

 从程序的运行结果来看,通过智能指针对内存资源的管理,尽管在类B构造函数抛出异常导致类B析构函数未被执行,但类A的析构函数仍然在对象pA生命周期结束时被调用,避免了资源泄漏。


析构函数中抛出异常?
在析构函数中是可以抛出异常的,但是这样做很危险,请尽量不要这要做。原因在《More Effective C++》中提到两个:
(1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
(2)通常异常发生时,c++的异常处理机制在异常的传播过程中会进行栈展开(stack-unwinding),因发生异常而逐步退出复合语句和函数定义的过程,被称为栈展开。在栈展开的过程中就会调用已经在栈构造好的对象的析构函数来释放资源,此时若其他析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃。


那么如果无法保证在析构函数中不发生异常, 该怎么办?

其实还是有很好办法来解决的。那就是把异常完全封装在析构函数内部,决不让异常抛出析构函数之外。这是一种非常简单,也非常有效的方法。


~ClassName()

{
    try {
        do_something();
    }
    catch(…) {  //这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外

    }

}

综上:在构造函数和析构函数中抛出异常会导致内存泄漏等问题出现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值