08 September 2022

C++ 核心指南目录

“A destructor must not fail”

理由

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

例子

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

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

注意

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

注意

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

注意

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

注意

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

强化

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