- 我知道有许多函数示例永远不会抛出,但编译器无法自行确定。在所有这些情况下,我应该在函数声明中附加 noexcept 吗?
noexcept
很棘手,因为它是函数接口的一部分。特别是,如果您正在编写一个库,您的客户端代码可以依赖于该noexcept
属性。以后可能很难更改它,因为您可能会破坏现有代码。当您实现仅由您的应用程序使用的代码时,这可能不是一个问题。
如果你有一个不能抛出的函数,问问你自己它是喜欢留下noexcept
还是会限制未来的实现?例如,您可能希望通过抛出异常(例如,用于单元测试)来引入对非法参数的错误检查,或者您可能依赖于可能更改其异常规范的其他库代码。在这种情况下,保守并省略 会更安全noexcept
。
另一方面,如果您确信该函数永远不应该抛出,并且它是规范的一部分是正确的,那么您应该声明它noexcept
。noexcept
但是,请记住,如果您的实现发生更改,编译器将无法检测到违规行为。
- 在哪些情况下我应该更加小心使用 noexcept,在哪些情况下我可以使用隐含的 noexcept(false)?
您应该专注于四类功能,因为它们可能会产生最大的影响:
- 移动操作(移动赋值运算符和移动构造函数)
- 交换操作
- 内存释放器(运算符删除,运算符删除 [])
- 析构函数(尽管这些是隐含的
noexcept(true)
,除非你制作它们noexcept(false)
)
这些函数通常应该是noexcept
,并且库实现很可能可以使用该noexcept
属性。例如,std::vector
可以在不牺牲强异常保证的情况下使用非抛出移动操作。否则,它将不得不退回到复制元素(就像在 C++98 中所做的那样)。
这种优化是算法层面的,不依赖编译器优化。它可能会产生重大影响,尤其是在复制元素成本高昂的情况下。
- 使用 noexcept 后,我什么时候可以真正期望观察到性能改进?特别是,给出一个 C++ 编译器在添加 noexcept 后能够生成更好的机器代码的代码示例。
noexcept
反对无异常规范的优点throw()
是该标准允许编译器在堆栈展开时有更大的自由度。即使在这种throw()
情况下,编译器也必须完全展开堆栈(并且它必须按照对象构造的完全相反的顺序进行)。
noexcept
另一方面,在这种情况下,不需要这样做。没有要求必须展开堆栈(但仍然允许编译器这样做)。这种自由允许进一步的代码优化,因为它降低了总是能够展开堆栈的开销。
关于noexcept、堆栈展开和性能的相关问题详细介绍了需要堆栈展开时的开销。
我还推荐 Scott Meyers 的书“Effective Modern C++”,“第 14 条:声明函数 noexcept 如果它们不会发出异常”以供进一步阅读。