将派生实现标记为 noexcept 而接口不是的副作用是啥
Posted
技术标签:
【中文标题】将派生实现标记为 noexcept 而接口不是的副作用是啥【英文标题】:What are the side effects of marking a derived implementation noexcept while the interface is not将派生实现标记为 noexcept 而接口不是的副作用是什么 【发布时间】:2018-10-22 21:56:31 【问题描述】:我们有一个实现 IUnknown 的类(或我们不拥有的任何接口)。我们开始用 noexcept 标记我们的大多数/所有方法,以进行任何潜在的优化,因为无论如何我们都不会抛出任何异常;尽管我们可能依赖的一些库。有人提出了是否应该将 QueryInterface/AddRef/Release 标记为 noexcept 的问题,因为接口不是。
当只有部分派生类被标记为 noexcept 时,是否有任何副作用或陷阱?
【问题讨论】:
请注意noexcept
不是免费的。把它放在所有功能上并不是一个好主意。代价是一旦戴上它就无法真正移除它。其他noexcept
代码可能依赖于您的函数的noexcept
,因此为了向后兼容,您会被卡住。这意味着您永远无法将实现更改为可能抛出的东西。添加noexcept
是一条单向街道,可以安全添加(如果正确),但删除不安全。您必须确保该函数本质上永远不会抛出。
【参考方案1】:
一般来说,您应该小心noexcept
。除非编译器可以证明该函数确实不会抛出任何异常,否则它必须插入一个动态处理程序以在发生异常时终止您的程序。因此,它不一定会导致您希望的优化。无论如何,将其添加到AddRef
、Release
和QueryInterface
应该是安全的。
编辑
例如,考虑以下代码:
extern int Foo();
int Bar() noexcept
return Foo();
这是 Clang 7.0 在 O3 上生成的:
Bar(): # @Bar()
push rax
call Foo()
pop rcx
ret
mov rdi, rax
call __clang_call_terminate
__clang_call_terminate: # @__clang_call_terminate
push rax
call __cxa_begin_catch
call std::terminate()
如果你删除noexcept
,你会得到这个:
Bar(): # @Bar()
jmp Foo() # TAILCALL
在此示例中,主要效果只是使图像有点膨胀,但请注意,对 Foo
的调用也变得有点效率低下。
【讨论】:
“插入动态处理程序”是什么意思?它如何影响性能? 捕获异常并调用std::terminate
是一小段代码。这很少成为问题。【参考方案2】:
我总是建议尽可能添加 noexcept。
如果您将它添加到某个位置并因此而崩溃,那么您刚刚了解到您不能在那里使用它以及原因。
【讨论】:
上报的 RCA:工程师了解到他们不能在这个关键业务功能上使用 noexcept。以上是关于将派生实现标记为 noexcept 而接口不是的副作用是啥的主要内容,如果未能解决你的问题,请参考以下文章