- 有很多函数例子我知道不会抛出异常,但编译器无法自己判断。在这种情况下,我应该在所有函数声明中附加
noexcept
吗?
noexcept
是棘手的,因为它是函数接口的一部分。特别是,如果你正在编写一个库,客户端代码可以依赖于noexcept
属性。如果要更改它可能会很困难,因为您可能会破坏现有代码。当您正在实现仅由您的应用程序使用的代码时,这可能不太重要。
如果您有一个不会抛出异常的函数,请问自己它是否会始终保持noexcept
,或者它会限制未来的实现?例如,您可能希望通过抛出异常(例如用于单元测试)引入非法参数的错误检查,或者您可能依赖于其他库代码,该代码可能更改其异常规范。在这种情况下,保守起见,最好省略noexcept
。
另一方面,如果您确信函数不会抛出异常并且它是规范的一部分,则应将其声明为noexcept
。但请记住,如果您的实现发生更改,编译器将无法检测到noexcept
的违规。
- 在哪些情况下应该更加谨慎地使用
noexcept
,在哪些情况下可以省略明确声明noexcept(false)
?
有四类函数应该特别关注,因为它们可能会产生最大的影响:
- 移动操作(移动赋值运算符和移动构造函数)
- 交换操作
- 内存释放器( operator delete,operator delete[])
- 析构函数(虽然这些隐式为
noexcept(true)
,除非您将其设为noexcept(false)
)
这些函数通常应该使用noexcept
,并且大多数库实现都可以利用noexcept
属性。例如,std::vector
可以在不损失强异常保证的情况下使用非抛出移动操作。否则,它将不得不回退到复制元素(就像在 C++98 中一样)。
这种优化是在算法级别上进行的,不依赖于编译器优化。它可能会有显着的影响,特别是当元素很难复制时。
- 我什么时候可以现实地期望使用
noexcept
后能观察到性能改进?请给出一个具体的例子,说明在添加noexcept
后C++编译器能够生成更好的机器代码。
noexcept
相对于没有异常规定或throw()
的优势在于标准允许编译器在堆栈展开方面有更大的自由度。即使是在throw()
的情况下,编译器也必须完全展开堆栈(并且必须按照对象构造的完全相反顺序来进行展开)。
另一方面,在noexcept
的情况下,不需要这样做。不存在必须展开堆栈的要求(但编译器仍然允许这样做)。这种自由度可以进一步优化代码,因为它降低了总是能够展开堆栈的开销。
有关必须进行堆栈展开时的开销的相关问题,请参见 noexept、堆栈展开和性能。
我还推荐Scott Meyers的书《Effective Modern C++》,其中“第14项:如果它们不会引发异常,请声明函数为noexcept”可供进一步阅读。
move_if_nothrow
(或类似的函数),并且存在一个noexcept
移动构造函数,那么将会提高性能。 - R. Martinho Fernandes