C++构造函数和析构函数中抛出异常的注意事项

本文探讨了C++中构造函数和析构函数抛出异常的风险及应对策略,强调了防止内存泄露和程序崩溃的重要性。

从语法上来说,构造函数和析构函数都可以抛出异常。但从逻辑上和风险控制上,构造函数和析构函数中尽量不要抛出异常,万不得已,一定要注意防止资源泄露。在析构函数中抛出异常还要注意栈展开带来的程序崩溃。

1.构造函数中抛出异常

在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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

程序输出结果: 
in A constructor 
in C constructor 
in B constructor 
in C destructor 
in A destructor 
catched

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

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

2.析构函数中抛出异常

在析构函数中是可以抛出异常的,但是这样做很危险,请尽量不要这要做。原因在《More Effective C++》中提到两个:

(1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。

(2)通常异常发生时,c++的异常处理机制在异常的传播过程中会进行栈展开(stack-unwinding),因发生异常而逐步退出复合语句和函数定义的过程,被称为栈展开。在栈展开的过程中就会调用已经在栈构造好的对象的析构函数来释放资源,此时若其他析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃。

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

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

~ClassName()
{
 try{
      do_something();
  }
  catch(…){  //这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在面对析构函数中抛出异常时,程序猿要注意以下几点:

(1)C++中析构函数的执行不应该抛出异常;

(2)假如析构函数中抛出了异常,那么你的系统将变得非常危险,也许很长时间什么错误也不会发生;但也许你的系统有时就会莫名奇妙地崩溃而退出了,而且什么迹象也没有,不利于系统的错误排查;

(3)当在某一个析构函数中会有一些可能(哪怕是一点点可能)发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外。

一定要切记上面这几条总结,析构函数中抛出异常导致程序不明原因的崩溃是许多系统的致命内伤!

C++中,构造函数析构函数在语法上都可以抛出异常,但从逻辑风险控制角度,应尽量避免。 ### 构造函数抛出异常 - **情况**:构造函数在执行过程中,若遇到无法处理的错误,如资源分配失败、初始化条件不满足等,可能会抛出异常。例如,在构造函数中动态分配内存时,若内存不足就可能抛出异常。 - **影响**:当构造函数抛出异常时,对象的析构函数将不会被执行,任何在构造函数中分配但未完全初始化的资源(如动态内存)将无法自动释放,可能导致资源泄漏 [^4][^5]。 ```cpp class MyClass { public: MyClass() { resource = new int[100]; if (/* some condition */) { throw std::runtime_error("Error in constructor"); } // 如果在这里抛出异常,resource 将不会被释放 } private: int* resource; }; ``` - **处理方法**:可采用RAII(Resource Acquisition Is Initialization)原则,尽量使用RAII技术来管理资源,确保即使发生异常也能自动释放资源。例如,使用智能指针代替原始指针 [^5]。 ```cpp #include <memory> class MyClass { public: MyClass() : resource(std::make_unique<int[]>(100)) { if (/* some condition */) { throw std::runtime_error("Error in constructor"); } // 使用智能指针,即使抛出异常也会自动释放资源 } private: std::unique_ptr<int[]> resource; }; ``` ### 析构函数抛出异常 - **情况**:析构函数在释放资源过程中,若遇到无法处理的错误,如关闭文件失败、释放内存时出现错误等,可能会尝试抛出异常。 - **影响**:若析构函数抛出异常,则异常点之后的程序不会被执行,如果析构函数异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会被执行,会造成诸如资源泄露的问题。通常异常发生时,C++机制会调用已经构造对象的析构函数来释放资源,此时如析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃 [^2][^4]。 - **处理方法**:当析构函数中有一些可能发生的异常时,要把可能发生的异常完全封装在析构函数内部,决不能让它抛出到函数之外。可使用try - catch代码块进行捕获处理异常 [^3][^4]。 ```cpp class MyClass { public: ~MyClass() { try { // 可能抛出异常的操作 } catch (...) { // 处理异常 } } }; ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值