【问题标题】:Wouldn't it make sense to overload with respect to noexcept?对 noexcept 进行重载不是很有意义吗?
【发布时间】:2018-01-18 19:05:50
【问题描述】:

我正在尝试了解 noexcept 功能。 我知道这可能会造成混淆,但除此之外,如果可能的话,可以从调用函数中推断出 noexcept。

这是这种情况的一个无效示例,

void f(){}
void f() noexcept{} // not allowed in c++

void g(){f();} // should call f
void h() noexcept{f();} // should call f noexcept
int main(){
    g();
    h();
}

如果调用函数 (h) 中没有 try/catch 块,则编译器可以推断出有兴趣调用特定的 f。

此模式是否用于其他解决方法?

我能想象的都是这样的,但它不是很通用:

template<bool NE> void F() noexcept(NE);

template<>
void F<true>() noexcept(true){}
template<>
void F<false>() noexcept(false){}

void g(){F<noexcept(g)>();} // calls F<false>
void h() noexcept{F<noexcept(h)>();} // call F<true>

有些人可能想知道为什么这样做有意义。 我的逻辑是 C++ 允许对 const 进行重载,既是函数的参数,也是成员函数。 例如,const 成员函数更喜欢调用 const 成员重载。

我认为noexcept 函数调用noexcept“重载”是有意义的。特别是如果它们不是从 try/catch 块中调用的。

【问题讨论】:

  • 使用 c++17,您可以使用 if constexpr,如:template&lt;bool NE&gt; void F() noexcept(NE) { if constexpr(NE) { ... } else { ... } }。不确定它对您的问题是否有用。
  • 这种重载的用例是什么?无论f 正在做什么都不会失败,在这种情况下不需要noexcept(false) 版本,或者它可能会失败,因此noexcept(true) 版本需要某种方式来报告该失败,这通常意味着不同的签名。
  • "如果调用函数 (h) 中没有 try/catch 块,那么编译器可以推断出有人对调用特定 f 感兴趣。"大多数异常安全代码没有 try 块;它依赖于 RAII。它仍然想调用 noexcept(false) 版本。
  • 如果你想要版本,一个通过返回值报告错误,另一个通过异常报告,看看std::nothrow,以及它是如何使用的。
  • @Deduplicator,非常好。不知道std::nothrow,这似乎是要遵循的成语。

标签: c++11 overloading overload-resolution noexcept c++17


【解决方案1】:

有道理,

原则上当然是有道理的。例如,该函数的一个版本可以运行更快的算法,但需要动态分配额外的暂存内存,而 noexcept 版本可以使用较慢的算法,在堆栈上具有 O(1) 额外空间。

但无法解决过载问题...

您可能知道,从noexcept(true) 函数调用noexcept(false) 函数是完全有效的。您只是冒着terminate 的风险,而不是抛出异常;有时 - 您不会冒任何风险,因为您已验证您传递的输入不会触发异常。那么,编译器怎么知道你调用的是哪个版本的函数呢?另一个方向也有同样的问题 - 也许你想从 noexcept(false) 函数中调用你的 noexcept(true) 函数?这也是允许的。

...而且 - 它主要是语法糖

使用 C++11,您可以编写:

#include <stdexcept>

template <bool ne2>
int bar(int x) noexcept(ne2);

template<> int bar<true>(int) noexcept { return 0; }
template<> int bar<false>(int) { throw std::logic_error("error!"); }

这编译得很好:GodBolt

因此,您可以拥有两个具有相同和相同参数的函数,仅在 w.r.t 方面有所不同。他们的 noexcept 值 - 但具有不同的模板参数。

【讨论】:

    【解决方案2】:

    我认为 noexcept 的重载本身并没有多大意义。当然,你的函数 f 是否为 noexcept 是有意义的,尤其是从 h 调用时,因为 h 需要捕获可能的异常并调用 std::abort

    然而,仅仅在 noexcept 上重载并不是一件好事。这就像在标准库中禁用异常。我并不是说您不应该这样做,但是,您因此而松散了功能。例如:std::vector::at 如果索引无效则抛出。如果禁用异常,则没有其他方法可以使用此功能。

    因此,如果您真的想要 2 个版本,您可能需要使用其他替代方法来指示失败。 std::可选,std::expected,std::error_code ...

    即使你设法在 noexcept 上重载,你的函数也会有不同的返回类型。这不是我作为用户从你的框架中所期望的。

    因此,我认为重载是一种不同的方式更好,因此用户可以选择使用哪个变体,这通过显式使用布尔值 std::nothrow 作为带有 std::error_code 的参数输出参数。或者,您应该选择您在库中使用的错误处理策略,并将其强制执行给您的用户。

    【讨论】:

      猜你喜欢
      • 2014-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-02
      • 2016-11-11
      • 1970-01-01
      相关资源
      最近更新 更多