首页 > C++编程 > 在构造函数抛出异常后析构函数将不再被调用

在构造函数抛出异常后析构函数将不再被调用

我发现要从构造函数中将错误信息传递给类的使用者有一个很简便的方法,那就是抛出异常,这样就可以直接传递字符串之类的信息了。如果是从一个普通的函数传回错误信息的话,以前往往选择返回一个错误代码,然后定义一个字符串二维数组,用相应的错误代号来取得错误信息。

但是如果构造函数要是抛出了异常,这个类的析构函数将不会被调用。如果不知道这一点,那么在构造函数中抛出异常就很危险了。析构函数不被调用,意味着清理工作不会被执行,会导致内存泄露、资源泄露等,这会给我们的产品带来很差的用户体验。

还是先来测试一下吧,证明这个结论是正确的:

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
// Coded by 代码疯子
// Blog: http://www.programlife.net/
// 构造函数抛出异常测试代码
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
 
class MyExcept
{
public:
	explicit MyExcept(string err) : errmsg(err) {}
	string getErrMsg() { return errmsg; }
private:
	string errmsg;
};
 
class Demo
{
public:
	Demo () { throw MyExcept("Throw exception in constructor"); }
	~Demo() { cout << "Will destructor be called? "; }
};
 
int main(int argc, char **argv)
{
	try
	{
		Demo d;
	}
	catch (MyExcept& e)
	{
		cout << e.getErrMsg() << endl;
	}
	return 0;
}

执行这段代码所生成的可执行文件,发现析构函数是不会被调用的。那么如果在普通成员函数抛出异常呢?析构函数会不会执行呢?(这里所讨论的情况是保证构造函数不会抛出异常,即在构造函数中不去调用可能抛出异常的函数(即便调用了也不抛出异常的情况))。

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
41
42
43
44
// Coded by 代码疯子
// Blog: http://www.programlife.net/
// 成员函数抛出异常测试代码
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
 
class MyExcept
{
public:
	explicit MyExcept(string err) : errmsg(err) {}
	string getErrMsg() { return errmsg; }
private:
	string errmsg;
};
 
class Demo
{
public:
	void except_test()
	{
		throw MyExcept("Throw exception in member function");
	}
	Demo () {}
	~Demo()
	{
		cout << "Will destructor be called? " << endl;
	}
};
 
int main(int argc, char **argv)
{
	try
	{
		Demo d;
		d.except_test();
	}
	catch (MyExcept& e)
	{
		cout << e.getErrMsg() << endl;
	}
	return 0;
}

在C++成员函数中抛出异常,析构函数被调用
可以看到析构函数在离开try块的时候调用了,然后进入catch块输出异常信息。

那如果一定要在构造函数中抛出异常怎么办?我觉得,可以把清理工作放到一个单独的函数中,在构造函数中抛出异常之前先调用这个函数进行清理工作。当然,在析构函数中也要调用这个函数(这样正常执行析构函数时也可以清理了)。


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> 在构造函数抛出异常后析构函数将不再被调用
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


更多



  1. 2011年5月31日07:17 | #1

    嗯,在.NET中会有自动回收机制,应该不会出现这问题,所以一般也很少使用析构函数来做回收的用法,呵。再不是的话,就在catch里加个finally应该也可以吧?

    [回复]

  2. 2011年6月1日18:11 | #2

    @代码部落(Winson)
    Java和.NET都有自动垃圾回收。使用起来更安全啦。不过C++的异常处理里面没有finally。在C++里面的析构函数中释放资源是经常做的。要Windows的SEH机制才支持

    __try{
     
    }
    __except()
    {
     
    }
    __finally
    {
     
    }

    话说你的评论怎么跑到垃圾评论里边去了呢? [em013]

    [回复]

  3. 2011年6月2日11:28 | #3

    其实感觉构造函数中就抛出异常,说明对象都没有被构造,也就没有生成这个对象,当然也就不会调用析构函数了……不知这么理解有没有误。但资源是否被释放,不知你测试过么?

    [回复]

  4. 2011年6月2日13:09 | #4

    @Eddy

    其实感觉构造函数中就抛出异常,说明对象都没有被构造

    这要看情况吧。比如说你构造了一部分之后抛出异常了,前面那部分还是构造了,而抛出异常之后那部分就没有被构造。我记得有在构造函数初始化列表中使用try的。资源应该不会自动释放吧,具体我也没有检测。不过你主动去释放肯定更加安全

    [回复]

    Jelly 回复:

    @代码疯子,
    按照C++的语义来分析,就是构造函数失败了,就不需要析构了,大可不必考虑到“构造一半”这种奇葩问题,如果出现了构造一半这种奇葩问题,那就说明是设计的问题。

    具体的关于这种问题,楼主可以参考Cocos2d-x中的二级构造这种方法。

    [回复]

  5. 2011年7月12日00:49 | #5

    构造函数失败就表示构造不成功,因此这个对象并没有“存在”。如果让编译器生成自动调用析构函数反而会导致无法预期的行为。
    补充一下,考虑到类里面可能还有属性成员、继承类,这个类构造异常抛出时会自动反顺序的调用(如果中途都没有匹配到合适的try块):
    构造函数内的异常处以上的所有局部变量的析构函数-〉属性成员的析构函数-〉继承类的析构函数
    如果某个成员或继承类在自己的构造期间抛出异常,那之前成功构造的东东析构函数们都会自动调用。但是不管怎样抛出异常的那个的析构函数在构造未返回时异常是不会调用析构函数的

    [回复]

  6. ligand
    2012年12月30日13:08 | #6

    构造函数中抛出异常,可以用try/catc包住构造函数中会抛出异常的地方,并且用RAAI包住构造函数中已经分配的资源。这样构造函数中抛出异常就不会导致资源孤儿了

    [回复]