将派生实现标记为 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。除非编译器可以证明该函数确实不会抛出任何异常,否则它必须插入一个动态处理程序以在发生异常时终止您的程序。因此,它不一定会导致您希望的优化。无论如何,将其添加到AddRefReleaseQueryInterface 应该是安全的。

编辑

例如,考虑以下代码:

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 而接口不是的副作用是啥的主要内容,如果未能解决你的问题,请参考以下文章

是否允许 C++ 标准库实现加强 noexcept 规范?

这仍然是实现从抽象派生的桥接模式吗?

TreeView而不是WebView?

三:继承

使用标记接口而不是属性的令人信服的理由

如何让派生类使用基实现来满足接口