【问题标题】:noexcept practice for style and performance?noexcept 练习风格和性能?
【发布时间】:2015-08-05 07:38:06
【问题描述】:

我开始将noexcept 添加到我的代码中,但我想知道将其添加到内联函数中是否明智。我假设优化器在明显不需要时会忽略运行时检查......但从人类/风格的角度来看,是否值得将 noexcept 添加到诸如 getter、设置、增量函数等琐碎的函数中?我认为这是完全明显的视觉混乱。我正在讨论一个规则,即内联函数可以省略 noexcept,但正常的 .hpp/.cpp 函数如果不抛出则必须具有它。

其次,我有大量根本无法抛出的代码,因为它没有分配(在我的国际象棋引擎中),不包括 STL 或任何其他可能失败的东西,所以总是可以保证成功。 noexcept 不会因为运行时检查而减慢它的速度吗?有没有人使用宏在使用noexcept 进行DEBUG 构建之间切换,但切换到throw() 进行发布,这只是编译时的?

【问题讨论】:

  • code that can't throw at all because it has no allocations 分配是唯一的异常原因吗?
  • 什么“运行时检查”?
  • 这可能会有所帮助:Should I use noexcept for getters always?
  • @deviantfan:嗯,没有操作系统调用,没有分配......这是一个没有任何功能的库,可能会以任何方式失败。这是国际象棋的计算。对于计算库 imo 来说非常合理。
  • noexcept 函数不需要展开,因此不需要保持在可展开状态。这就是优化机会。

标签: c++ c++11 exception-handling coding-style noexcept


【解决方案1】:

如果您的内联函数是叶级函数,即它本身不调用任何函数,那么理论上编译器可以确定它不会抛出并忽略任何可能生成的异常处理。所以在性能方面,它可能被证明是不必要的。

话虽如此,您不应期望添加noexcept 会降低性能。添加noexcept 后,必须生成处理异常传播的任何代码都不应变得更加复杂。值得注意的是,如果 noexcept 函数抛出异常,则允许编译器完全省略展开堆栈。这在很大程度上是 noexcept 的直接好处的来源。

对于风格推荐,首先要考虑noexcept 是否会成为您界面的有用部分。出于算法原因,诸如移动操作之类的事情可以从noexcept 中受益匪浅,但除此之外,真正由您决定noexcept 对您、您的界面和界面用户的价值。

如果这不能回答您的问题,请随时评论我的回答,我会进一步澄清。

旁注:throw(),在 C++11 中也被弃用,不提供与 noexcept 相同的保证。如果通过声明为throw() 的函数抛出异常,则堆栈必须完全展开到该函数的调用者。有关此行为的参考,请参阅 C++ 标准 N3337 版本中的 15.5.2.1。

【讨论】:

  • 我猜VC++违反了标准,它一直基于throw()进行优化。见msdn.microsoft.com/en-us/library/wfa0edys.aspx。此外,您还没有解决处理运行时违反 noexcept 的潜在成本。我仍然认为单个编译器可能最终会为noexcept 的“仅编译时”版本提供一种方式,就像 VC++ 已经做到的那样,因此宏将是调整代码库的最佳方式编译器。
  • 考虑到异常传播的潜在运行时成本和必要的堆栈展开,如果可以在有问题的抛出点确定它将通过声明为noexcept 的函数传播,那么程序是允许立即终止,而不展开任何堆栈。对于基于表的异常处理方法,据我了解,这是可能的。比较遇到noexcept 和不遇到noexcept 情况,有可能生成更高效的代码。
  • 这是否会导致性能提升取决于您选择的编译器和相关程序的优化。当然,与 throw() 的 VC++ 行为相比,noexcept 在代码生成方面可能无法衡量,但遇到 noexcept 不会立即破坏您的程序。
  • 好的,感谢您确认我并没有疯狂地认为 noexcept 从零开销的角度来看有点不完美。虽然很挑剔,但我想我将使用宏观化的令牌来回交换,至少只是为了同时获得双向的大规模性能测量。将它添加到所有这些代码中需要一段时间。
  • 不用担心。如果您能够在其他平台上找到类似的功能,那么我可以看到这在某些情况下很有用。就throw() 的预期行为而言,noexcept 应该会更好。如果我提供的答案是可以接受的,请将其标记为已接受,以便其他寻求此问题答案的人受益。 :)
【解决方案2】:

通过向函数添加 noexcept 或 noexcept(true) 说明符,您要求编译器添加运行时检查并调用 std::terminate。所以你有一个小的性能影响。你应该从中有所收获,否则我看不到任何意义。标准库具有特征:

is_nothrow_constructible,
is_nothrow_default_constructible,
is_nothrow_move_constructible,
is_nothrow_copy_constructible,
is_nothrow_assignable,
is_nothrow_move_assignable,
is_nothrow_copy_assignable,
is_nothrow_destructible.

已知标准库中的容器在包含的类型上使用这些特征来执行优化(移动而不是复制)。也许其他一些优化,我不知道。对我来说,将 noexcept 说明符添加到适当的构造函数或赋值运算符(当然,如果它们真的不抛出)是有意义的,当将您的类与标准容器一起使用时,这些特征将返回 true,并获得性能提升。

我不认为将 noexcept 添加到 getter 或 setter 之类的普通函数是一个好主意:您会受到性能影响而一无所获。 如果您的代码没有抛出并且不使用标准库 - 我的建议:根本不要使用 noexcept 。

【讨论】:

  • 通过向函数添加 noexcept 或 noexcept(true) 说明符,您要求编译器添加运行时检查并调用 std::terminate。所以你的性能受到了一点影响。 不,你没有。 noexpect 被精心设计以引入 no 开销。 没有运行时检查,对std::terminate()的调用是在抛出未捕获异常的地方引入的。相反,noexcept 为编译器(甚至更多用户代码)提供了一些优化机会
  • 编译器不能变魔术。只有当函数不调用任何东西或只调用 noexcept 指定的函数时,noexcept 才没有开销。在任何其他情况下,编译器必须插入运行时检查以确定异常不会离开 noexcept 作用域。与没有任何异常说明符的代码相比,noexcept 为用户或编译器生成的代码提供了哪些优化机会?
  • 正如我和@Jared 的回答状态,当编译器遇到noexpect(与throw() 或没有注释相反)时允许执行的操作是假设没有堆栈展开地方。因此,与其生成代码来引发异常,不如展开堆栈,检查函数上是否存在throw() 并调用std::unexpected(),允许跳过所有这些,只需将throw jadajada 替换为对@987654330 的调用@
  • 用户生成的代码可以做与库相同的事情,即您在答案中描述的内容
  • 与 throw() 相比 yes 与无注释 no 相比。而且我看不出将函数标记为 noexcept 然后在其中抛出异常或调用可以抛出的其他函数有任何意义。 用调用 std::terminate() 替换 throw jadajada 这是优化吗?我对这个词的理解有些不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-20
  • 1970-01-01
  • 2017-03-02
  • 1970-01-01
  • 2011-05-09
  • 2011-03-25
相关资源
最近更新 更多