条款08:别让异常逃离析构函数

本文探讨了C++中析构函数抛出异常的风险及应对策略,提供了多种处理方法,包括异常捕获与记录、客户调用责任转移等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总结:1.在析构函数中尽量不要有抛出异常的事情发生,如果析构函数中抛出了异常,析构函数应该自己捕捉这个异常,然后根据程序需要终止程序或者吞下这个异常然继续执行程序

    2.如果需要客户对于某个操作的函数运行期间所抛出的异常做出反应,这个时候应该将这个抛出异常的 函数单独写在一个函数中,而不是在析构函数中执行。

打眼一看这两句话有点迷不知道几个意思,其实这个条款的核心内容就是告诉你尽量不要让异常发生在析构函数中。。然后举了一堆实际应用中这种事情该如何处理的例子。

下面我就说一下书中的具体内容吧。

为什么C++不禁止析构函数抛出异常,但是又不鼓励你去在析构函数中抛出异常呢?下面举个例子来说明(在这里就直接用书上的例子了)

class Widget
{
public:
	~Widget()
	{
		//假如这个析构函数抛出异常
	}
};
void dosomething()
{
	std::vector<Widget> v;
}
上述代码在v被销毁的时候会执行widget的析构函数,假如在第一个Widget这个析构函数中抛出了异常(假设vector中有10个Widget),情况好的话继续执行销毁后面的9个Widget,但是同样第二个Widget的析构函数一样会抛出异常,现在有两个同时作用的异常,这对于C++来说接受不了,程序肯定会出问题,要么是结束执行,要么是导致不明确行为,这个例子中的错误应该是导致不明确行为。注意这里不是说使用容器之类的才会发生这种事情,终点强调的是只要析构函数抛出异常,这种事情就会发生。所以说不要让析构函数抛出异常貌似就这么解释完了......


但是如果必须在析构函数中执行一个动作(可以说是调用一个函数完成一件事情),并且这个动作还会抛出异常那么我们应该怎么处理呢?..很蛋疼的问题。。

看下面例子

class DBConnection
{
public:
	static DBConnection create();//这个函数可以创建出DBConnection对象供其他需要的使用,
	void close();//执行关闭数据库的一个动作,这个动作可能抛出一个异常
};
上面的例子是模拟数据库链接的一个例子,其中的close这个函数就相当于关闭数据库的一个动作。但是如果这么写的话就需要客户自己去调用了,这样难免不了万一客户忘记调用发生的问题,所以解决方法是用其他类来管理DBConnection的方法,那就是在其他类的析构函数中调用close这个方法,给出具体代码来理解一下

class DBConn
{
public:
	DBConnection db;
	~DBConn()
	{
		db.close();//调用这个方法
	}
};
这样的话在DBConn对象被销毁的时候就会自动调用close这个方法了,这样就避免了用户忘记自己调用所产生的问题

书中还有这么一个代码

{
	DBConn dbc(DBConnection::create())//函数中的参数是DBConnection对象
		....//创建一个DBConnection对象交给DBConn对象管理
}
当然这只是演示关于两个类对象的一定=些东西。


如果上面的防止客户忘记调用的写法顺利执行close并且没有异常那最好,可是如果close抛出异常了怎么办呢?

解决方法1.在构造函数中捕捉这个方法,并且停止程序

class DBConn
{
public:
	DBConnection db;
	~DBConn()
	{
		try
		{
			db.close();//调用这个方法}
		}
		catch (...)
		{
			//记录异常信息
			std::abort();//结束程序
		}
	}
};
解决方法2:在构造函数中捕捉异常,吞并,但是不结束程序

class DBConn
{
public:
	DBConnection db;
	~DBConn()
	{
		try
		{
			db.close();//调用这个方法}
		}
		catch (...)
		{
			//记录异常信息
	       
		}
	}
};
这两个解决方式根据实际情况悠着不同的好坏。

其实这两个方法都很一般般,因为他们无法对抛出的异常情况进行一个反应(可以说是看见这个异常的发生吧)。所以一个比较好的方案就出来了

这个方案1.可以让客户自己调用这个方法,如果发生异常客户可以有机会处理2.如果客户没有自己调用程序就自己调用这个方法,如果抛出异常就记录下来根据实际情况结束或者继续执行程序。(返回之前的老路)

class DBConn
{
private:
	DBConnection db;
	bool closed;
public:
	void close()
	{
		db.close();//客户自己调用
		closed = true;//如果客户自己调用了析构函数中就不在调用
	}
	~DBConn()
	{
		if (!closed)//如果客户没掉用析构函数中就继续调用
		{
			try
			{
				db.close();//调用这个方法}
			}
			catch (...)
			{
				//记录异常信息

			}
		}
	}
};
这样写就可以将close的调用责任推给客户了,并且双保险(客户没调,自己调)。虽然这个接口不太好,但是如果某个行为抛出异常的话,而你又必须去处理这个异常,那么这个异常必须来自析构函数外面的某个函数(意味这自己调用,现在对开始的第二句话有一定的理解了吧)。因为析构函数吐出异常是危险的,会导致不明确的行为发生或者程序终止。所以这么写还可以甩锅给客户,他们有机会处理异常而自己不去处理,程序出问题了,怪谁呢?真的是机制的程序员。。。

这个条款整体说的就是这么些东西,如果有什么理解不对的地方欢迎大家留言,一起交流学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值