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

时间:2013-08-11 21:41:26

标签: c++ c++11 bad-alloc noexcept

假设你有一个通常永远不会失败的功能,例如:

std::string convert_integer_to_string(int x);

在市政当局,这将成为noexcept的候选人。但是,实现很可能涉及动态内存管理,因此在使用new运算符分配内存时,它总是会抛出std::bad_alloc

是否建议将该功能注释为noexcept?

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

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

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

2 个答案:

答案 0 :(得分:10)

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

特别是,使用noexcept作为虚假文档:如果函数实际可以抛出,则违反合同。如果发生此违规行为,系统会对某些定义的行为做出反应,这并不意味着您应该利用它。 ...虽然简单的程序可能无法恢复并且在内存耗尽时死亡,但真正的程序可能至少需要存储足够的状态来恢复它们在死亡时留下的混乱,即,它对于任何功能都是不可接受的做出关于杀死程序的决定(当然,除非记录该函数的意图)。

答案 1 :(得分:1)

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

在某些操作系统(至少是linux)下,当内存不足时,默认行为将被操作系统(oom killer)杀死。当您写入内存时(而不是在分配内存时)会发生这种情况,并且您将无法运行任何清理代码。此功能称为memory overcommit

即使您确实从内存中获取了已用完的信息,也很难正确处理这些错误:您需要确保您的异常处理程序不分配内存。这包括来自该错误处理程序的所有函数,还需要确保可能已经触发的任何通用异常处理程序(例如日志记录)不使用任何内存。在关闭程序之前,通常希望的最好的是一些简单的清理工作。

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