专栏首页C/C++基础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;
    }
}

程序输出结果: 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)C++中析构函数的执行不应该抛出异常;

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

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

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


参考文献

[1]Scott Meyers.More Effective C++[M].北京:电子工业出版社.2013[58-61] [2]http://www.cnblogs.com/fly1988happy/archive/2012/04/11/2442765.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Effective C++ 条款08:别让异常逃离析构函数

    《Effective C++》第三版中条款08建议不要在析构函数中抛出异常,原因是C++异常机制不能同时处理两个或两个以上的异常。多个异常同时存在的情况下,程序...

    Dabelv
  • C++inline函数简介

    inline函数是由inline关键字来定义,引入inline函数的主要原因是用它替代C中复杂易错不易维护的宏函数。

    Dabelv
  • Google C++编程风格指南(四)之类的相关规范

    类是C++中基本的代码单元,自然被广泛使用。本节列举了在写一个类时要做什么、不要做什么。

    Dabelv
  • 是否能在构造函数,析构函数中抛出异常?

      最近在工作中,接触到两次这个问题,一次是与Manager的每月一次交流中,Manager问我这个问题,当时回答得支支吾吾;另外一次是《Code View》学...

    宋凯伦
  • Linux系统备份与恢复

    1、如果系统未安装tar工具,时行安装     >yum -y install tar 2、新建一个要备份的文件夹     >mkdir -p /backup...

    Sindsun
  • 深度学习——RNN(1)RNN基础LSTM

    DC童生
  • 收藏!深度学习速查手册(文末附链接)

    手册下载链接:https://github.com/kailashahirwar/cheatsheets-ai

    朱晓霞
  • 【AI初识境】被Hinton,DeepMind和斯坦福嫌弃的池化,到底是什么?

    这一次咱们反着来,说说学术界对池化的最新观点。通常我们认为,池化可以增加网络对于平移的不变性,对于网络的泛化能力的提升是非常关键的。不过,到底能起到多大的正向作...

    用户1508658
  • 原 What Every Dev need

    魂祭心
  • 【AlphaGo Zero 核心技术-深度强化学习教程笔记06】价值函数的近似表示

    点击上方“专知”关注获取更多AI知识! 【导读】Google DeepMind在Nature上发表最新论文,介绍了迄今最强最新的版本AlphaGo Zero,不...

    WZEARW

扫码关注云+社区

领取腾讯云代金券