翻译《有关编程、重构及其他的终极问题?》——8.记住:析构函数中的异常是危险的

本文探讨了析构函数中抛出异常的风险,并提供了避免此类问题的解决方案。文章详细解释了为何析构函数内的异常会导致程序崩溃,并给出了几种推荐的做法。

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

翻译《有关编程、重构及其他的终极问题?》——8.记住:析构函数中的异常是危险的

标签(空格分隔): 翻译 技术 C/C++
作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
校验者:高国栋
最后更新:2016年12月08日


本书背景说明、总目录等介绍,可以跳转到以下链接进行查看:
http://blog.youkuaiyun.com/headman/article/details/53045891

欢迎大家转载,但请附上原作者以及翻译者的名字、原文出处,以尊重光荣的劳动者。


8.记住:析构函数中的异常是危险的

这次说的问题是在LibreOffice项目中发现的。这个错误的PVS-Studio诊断是这么描述的:V509 The ‘dynamic_cast < T&>’ operator should be located inside the try..catch block, as it could potentially generate an exception. Raising exception inside the destructor is illegal(译者注:大意是dynamic_cast

virtual ~LazyFieldmarkDeleter()
{
    dynamic_cast<Fieldmark&>
        (*m_pFieldmark.get()).ReleaseDoc(m_pDoc);
}

解释
当程序中抛出异常时,对应的线程栈就开始回滚,其中的对象就会自动调用析构函数以便释放自己。如果一个对象的析构函数在线程栈回滚时被调用然后抛出异常,C++标准库就会即可调用terminate()函数终止整个程序。所有就有了一条规则:析构函数中永远不要抛出异常,即使有异常也要在析构函数内部处理掉。

前面引用的代码是非常危险的。如果转换一个对象到指定类型失败,dynamic_case操作符将抛出std::bad_cast异常。

同样,其他的构建抛出异常也是危险的。比如,在析构函数中分配内存就不怎么安全,因为如果一旦失败,就会抛出一个std:bad_alloc异常。

正确的代码
如果使用dynamic_cast时不用引用而是指针就可以修复前面的问题,这样,如果转换对象类型失败,就不会抛出一个异常,而会返回nullptr。

virtual ~LazyFieldmarkDeleter()
{
    auto p = dynamic_cast<Fieldmark*>m_pFieldmark.get();
    if (p)
        p->ReleaseDoc(m_pDoc);
}

建议
让你的析构函数越简单越好,不要在其中分配内存或读文件。

当然,你不可能一直让析构函数保持简单,但我相信我们可以尽力接近这个目标。另外,一个复杂的构造函数,意味着类结构设计的贫乏,以及有缺陷的构思。

在析构函数中如果代码越多,一旦发生问题,那么就难以确认问题,因为就很难去辨别那些代码抛出或者不抛出异常。

如果一旦有异常在析构函数中抛出,一个通常的好办法就是用catch(…)在析构函数中处理掉它:

virtual ~LazyFieldmarkDeleter()
{
    try 
    {
        dynamic_cast<Fieldmark&>
            (*m_pFieldmark.get()).ReleaseDoc(m_pDoc);
    }
    catch (...)
    {
        assert(false);
    }
}

当然,这样使用会隐藏析构函数中的一些错误,但是,通常来说,却能让你的应用程序更稳定。

我并不是坚持一定不能在析构函数中抛出异常——这其实依赖于实际的情况,有时候在析构函数中抛出异常还是很有用的,我在一些十分特别的类中见过这种情况,这些类一般被设计成在析构时抛出异常,但这非常很少见。如果是类似“圈绳”,“点”,“画刷”,“三角”,“文档”等的类(译者注:这里作者举例了画板类的应用中的对象),那么就不该在析构函数中抛出异常(译者注:因为用户还没有保存文档,程序就可能异常退出了,结果你懂得)。

只要记住,两次嵌套的异常抛出就会导致程序退出,所以最后由你决定这种情况是否该在项目中发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值