永远不要将涉及动态内存分配的函数注释为noexcept

Never annotate functions involving dynamic memory allocation as noexcept?

本文关键字:分配 函数 注释 noexcept 内存 动态 永远      更新时间:2023-10-16

假设您有一个通常永远不会失败的函数,例如:

std::string convert_integer_to_string(int x);

从原则上讲,这将是noexcept的候选者。然而,实现很可能涉及动态内存管理,因此在使用new运算符分配内存时,它可能总是抛出std::bad_alloc。

是否建议将函数注释为noexcept

从实践的角度来看,以合理的方式处理记忆不足的情况是极其困难的。大多数程序只是假设有足够的内存可用。如果noexcept函数抛出std::bad_alloc,那么调用std::terminate在这种情况下似乎是合理的。

对我来说,noexcept是某种形式的文档。这是一个承诺,您(或优化器)可以放心地假设此函数永远不会抛出。如果您正在编写一个不关心内存不足情况的应用程序,那么这仍然是一个有效的假设。

我想最安全的建议是,如果可能引发std::bad_alloc异常,则永远不要使用noexcept。另一方面,我想知道使用noexcept是否有好处,假设您不关心内存不足的情况(即std::terminate是否可以)。

如果函数可以出于任何原因抛出异常,即使它是std::bad_alloc,也应该不要将其声明为noexcept。相对而言,很少有函数真的不能抛出异常,而且它在哪里也很重要。noexcept函数的主要需求是允许在出现异常时检测可用的错误恢复选项:例如,std::vector<T, A>在插入对象时可以使用移动构造,假设移动构造不会抛出。如果移动构造可以抛出,则在实现强异常安全操作时,移动对象不能用于恢复异常。因此,如果类型T的移动构造可能失败,则std::vector<T, A>的实例化不能移动对象,而是需要复制它们。

特别是,不要使用noexcept作为虚假文档:如果函数真的可以抛出,那就违反了约定。事实上,在发生这种漏洞的情况下,系统会对某种程度的定义行为做出反应,这并不意味着你应该利用它……虽然简单的程序可能不会恢复,只是在内存耗尽时死亡,但真正的程序可能至少需要存储足够的状态来恢复它们在死亡时留下的混乱,即。,任何函数都不能决定终止程序(当然,除非这是函数的意图)。

我不确定我是否会太担心内存不足的异常。

在一些操作系统(至少是linux)下,当内存耗尽时,默认行为是被操作系统杀死(被oom杀手杀死)。当您向内存写入时(而不是在分配内存时)就会发生这种情况,并且您将没有机会运行任何清理代码。此功能称为内存过度使用

即使你得到了内存耗尽的信息,也很难正确处理这些错误:你需要绝对确保你的异常处理程序不会分配内存。这包括错误处理程序中的所有函数,您还需要确保在此过程中可能触发的任何通用异常处理程序(例如日志记录)都不使用任何内存。你通常希望的最好的办法是在关闭程序之前进行一些简单的清理。

请注意,您也可以使用std::nothrow来检查分配的结果,而不使用异常(也就是说,如果您的操作系统在分配时实际告诉您该信息)。当你做一个你认为可能会失败的大分配时,这样做可能是有意义的。它还有一个很好的特性,即您将获得一个非常容易调试的nullptr,而不是处理(潜在的)未捕获的异常。