我应该什么时候使用noexcept?
noexcept
关键字可以适用于许多功能签名,但我不确定何时应该考虑在实践中使用它。 根据我目前阅读的内容, noexcept
的最后一刻添加似乎解决了移动构造函数抛出时出现的一些重要问题。 但是,我仍然无法为一些实际问题提供令人满意的答案,这些问题让我首先阅读了更多关于noexcept
的内容。
有很多函数的例子,我知道永远不会抛出,但编译器无法自行确定。 在所有这些情况下,我是否应该在函数声明中添加noexcept
?
不得不考虑在每个函数声明之后是否需要附加noexcept
,这将大大降低程序员的生产力(坦率地说,这将是一个痛苦的屁股)。 对于哪种情况,我应该更加小心使用noexcept
,并且在这种情况下,我能否隐含noexcept(false)
?
什么时候可以切实地期望在使用noexcept
之后观察性能改进? 具体来说,给出一个代码,在添加noexcept
之后,C ++编译器能够生成更好的机器代码。
就我个人而言,我关心的是noexcept
因为为编译器提供了更多的自由度来安全地应用某些类型的优化。 现代编译器以这种方式利用noexcept
吗? 如果不是,我希望他们中的一些人能够在不久的将来这样做吗?
我认为现在提供“最佳实践”答案为时尚早,因为实践中没有足够的时间来使用它。 如果在问题出来之后询问关于throw说明符的问题,那么答案将与现在非常不同。
不得不考虑在每个函数声明之后是否需要附加noexcept
会大大降低程序员的生产力(坦率地说,这将是一个痛苦)。
那么很明显,这个函数永远不会抛出。
什么时候可以切实地期望在使用noexcept
之后观察性能改进? [...]个人而言,我关心noexcept
因为提供给编译器的安全应用某些优化的自由度增加了。
看起来最大的优化收益来自用户优化,而不是编译器的优化,因为可能会检查noexcept
和重载。 大多数编译器遵循一个没有惩罚 - 如果你不投掷的异常处理方法,所以我怀疑它会在你的代码的机器代码级别上改变很多(或者任何东西),尽管可能通过删除处理来减少二进制大小码。
使用noexcept
在大4(构造函数,赋值,析构函数不是因为他们已经noexcept
)可能会造成最好的改进作为noexcept
支票模板代码中“普通”,如性病容器。 例如, std::vector
不会使用类的移动,除非它被标记为noexcept
(否则编译器可以推导出它)。
正如我不断重复这些日子: 首先是语义 。
添加noexcept
, noexcept(true)
和noexcept(false)
首先是关于语义的。 它只是偶然地限制了一些可能的优化。
作为程序员的阅读代码, noexcept
的存在类似于const
:它可以帮助我更好地理解可能发生或可能不发生的事情。 因此,花一些时间考虑是否知道函数是否会抛出是值得的。 对于提醒,任何类型的动态内存分配可能会抛出。
好的,现在进行可能的优化。
最明显的优化实际上是在库中执行的。 如果可能的话,C ++ 11提供了许多可以知道函数是否为noexcept
,而标准库实现本身将使用这些特征来支持对他们操作的用户定义对象执行noexcept
操作。 如移动语义。
编译器可能只会从异常处理数据中删除一点(可能),因为它必须考虑到您可能撒谎的事实。 如果标记为noexcept
的函数抛出,则调用std::terminate
。
选择这些语义有两个原因:
noexcept
受益,即使依赖项已经不使用它(向后兼容) noexcept
这实际上确实对编译器中的优化器产生了(可能)巨大的差异。 通过函数定义之后的空throw()语句以及适当的扩展,编译器实际上已经有了这个功能多年。 我可以向你保证,现代编译器确实可以利用这些知识来生成更好的代码。
几乎在编译器中的每一个优化都使用了一个叫做流程图的函数来推断什么是合法的。 流程图包含通常称为功能块(具有单个入口和单个出口的代码区域)以及块之间的边界以指示流可以跳转到的位置。 Noexcept改变流程图。
你问了一个具体的例子。 考虑这个代码:
void foo(int x) {
try {
bar();
x = 5;
// other stuff which doesn't modify x, but might throw
} catch(...) {
// don't modify x
}
baz(x); // or other statement using x
}
如果该函数的流图是不同的bar
标记为noexcept
(没有办法以供执行的端部之间跳bar
和catch语句)。 当标记为noexcept
,编译器在baz函数期间确定x的值是5 - x = 5块被称为“控制”baz(x)块而没有从bar()
到catch语句的边缘。 然后它可以做一些名为“不断传播”的东西来生成更高效的代码。 在这里,如果baz被内联,那么使用x的语句也可能包含常量,然后过去的运行时评估可以转化为编译时评估等。
无论如何,简短的回答: noexcept
让编译器生成更紧密的流程图,流程图用于noexcept
各种常见的编译器优化。 对于编译器来说,这种性质的用户注释非常棒。 编译器会试图找出这些东西,但它通常不能(有问题的函数可能在另一个对象文件中,而编译器不可见或者传递地使用某些不可见的函数),或者当它存在时可能会抛出一个无关紧要的异常,因为它不会隐式地将其标记为noexcept
(例如,分配内存可能会抛出bad_alloc)。