08 September 2022

C++ 核心指南目录

C.36: A destructor must not fail

理由

如果析构函数会执行失败,我们就不知道怎么写出不出错的代码了。标准库要求所有跟它配合的类不能有出错时抛出异常的析构函数。

例子

class X {
public:
    ~X() noexcept;
    // ...
};

X::~X() noexcept
{
    // ...
    if (cannot_release_a_resource) terminate();
    // ...
}

注意

很多人尝试析构函数出错的防呆策略,但是都失败了。这其中有一个技术实践问题:比如,如果一个套接字无法关闭,会发生什么?编写析构函数的人不知道什么时候会调用,不能通过抛出异常来“拒绝执行”。如果套接字的“关闭/释放”操作不支持重试,问题会更麻烦。如果可能的话,尽量考虑在资源关闭或清理过程中出现的错误为一种基础设计错误,直接终止程序。

注意

声明析构函数为 noexcept 。这样可以确保要么正常结束,要么终止整个程序。

注意

如果资源无法释放,但是程序不能终止,那么就把故障通知到系统的其他部分。比如修改某个全局状态,期待某些地方会注意到这个错误,对故障进行处理。请注意,这个技术是一种特殊的处理过程,非常容易出错。比如“我的连接无法关闭”这个例子。连接出错,可能是因为连接的另一个端点有问题,代码很难同时处理连接两端的故障。析构函数必须能够传递某个消息给系统中处理这部分错误的管理任务。让其考虑如何关闭连接,恢复网络的正常工作。

注意

如果析构函数使用了执行会失败的操作,那么函数要能捕捉到异常,某些情况下要确保能成功完成执行。(比如使用抛出异常操作的某些不同的清理机制)。

强化

  • (简单)析构函数必须声明为 noexcept