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

来源:互联网 发布:excel数据有效性在那 编辑:程序博客网 时间:2024/05/18 01:28

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

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


本书背景说明、总目录等介绍,可以跳转到以下链接进行查看:
http://blog.csdn.net/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);    }}

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

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

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

0 0