【问题标题】:Can a noexcept function still call a function that throws in C++17?noexcept 函数仍然可以调用在 C++17 中抛出的函数吗?
【发布时间】:2023-04-03 16:03:01
【问题描述】:

P0012R1,“让异常规范成为类型系统的一部分”,
我看到noexcept 现在正在成为函数类型的一部分。

我不知道这是否会阻止noexcept(true) 函数仍然能够调用noexcept(false) 函数。

以下代码对 C++17 是否仍然有效?

void will_throw() noexcept(false){
  throw 0;
}

void will_not_throw() noexcept(true){
  will_throw();
}

【问题讨论】:

  • 如果抛出函数在 noexcept 函数的try...catch 内怎么办?
  • noexcept 表示函数不抛出,而不是不能抛出,不遵守的惩罚是调用@987654328 @,不是UB。所以是的,该代码是合法的。

标签: c++ exception function-pointers c++17 noexcept


【解决方案1】:

根据cppreference

请注意,函数上的 noexcept 规范不是编译时 查看;它只是程序员通知编译器的一种方法 函数是否应该抛出异常。

所以你的代码语法是有效的,但是std::terminate会在执行时被调用。

【讨论】:

  • 是否存在至少对此类行为发出警告的编译器标志?
  • 考虑到noexcept 不是编译时检查,似乎不可能要求编译器生成警告。
【解决方案2】:

不幸的是它在编译时有效。

虽然编译器使用 noexcept 来优化异常处理代码,使代码性能更高,但遗憾的是他们没有进一步推动它给 noexcept语义。

理想情况下,当您将方法标记为 noexcept 时,它应该也意味着该方法不应该让任何异常冒泡。因此,如果您有一个标记为 noexcept 的方法,但它调用了未标记为 noexcept 的其他方法,那应该会给您一个编译错误,除非有一个 try/catch 阻止任何可以抛出的东西。

简单地调用 std::terminate 是一个非常糟糕的语言设计选择,因为它不会让编写 noexcept 方法的人承担任何责任。相反,它甚至使消费者无法解决问题,从而损害了软件的重用。

例如,假设我是一个糟糕的库开发人员,我编写了以下代码:

我的库附带的头文件 Foo.h

class Foo
{
public:
    void DoSomething() noexcept;
};

您是 FooLib 编写 Bar 应用程序的快乐消费者:

Bar.cpp

#include "Foo.h"

int main()
{
    Foo foo;

    try
    {
        foo.DoSomething();
    }
    catch (...)
    {
        std::cout << "Yay!" << std::endl;
    }

    return 0;
}

该代码可以正常编译并正常运行,直到您有 Foo 抛出异常...如果您将 foo.DoSomething() 的调用用 括起来,则没有任何区别>try/catch 块。代码将简单地中止。

如果您没有 Foo 的代码,则无法修复它。在这种情况下,唯一的解决方案是丢弃 Foo 库并编写自己的库。

Foo.cpp 的内容可以是这样的:

static void PotentiallyThrowException()
{
    throw 0;
}

void Foo::DoSomething() noexcept
{
    PotentiallyThrowException();
}

请注意,由 Foo::DoSomething() 的实现者将自己的调用包装到 try/catch 中。但是由于同样的问题,如果他们正在调用标记为 noexcept 的其他方法并且那些开发人员没有这样做,那么现在是 Foo::DoSomething()那是软管。以此类推。

我们可以肯定地说,从语义的角度来看,noexcept 不仅无用,而且有害。

【讨论】:

    【解决方案3】:

    noexcept(true) 函数可以调用noexcept(false) 函数。如果抛出异常,将会出现运行时错误。允许这样做的典型示例是:

    double hypotenuse(double opposite, double adjacent) noexcept(true)
    {
        return std::sqrt(opposite*opposite + adjacent*adjacent);
    }
    

    std::sqrt 将抛出 domain_error 如果它的论点是否定的,但显然这里永远不会发生。

    (在理想情况下,默认情况下会禁止 exception_cast 在需要时允许它。如果抛出异常,结果可能是 UB,或者 std::terminate)。

    【讨论】:

      【解决方案4】:

      在函数类型中包含异常规范与一个函数是否可以调用另一个具有不兼容异常规范的函数(不处理未包含在其异常规范中的异常)是正交的。前者是关于函数指针的类型安全(因此您不能通过已知不会抛出的函数指针调用抛出函数)。对于后者,我们可以在编译期间禁止它(如在 Java 中),或将其视为运行时错误(导致程序终止,如 C++ 当前标准所选择的那样)。

      人们可能会通过从const(非静态)成员函数调用非const(非静态)成员函数的类比来争论。然而不同之处在于,间接修改通过const 成员函数调用的非const 成员函数内的对象将不会被检测到(否则检测成本太高)并且可能导致讨厌的错误,这就是它有在编译期间要防止。而抛出异常的行为是(应该是)异常事件,我们可以插入运行时检查异常是否符合异常规范并且应该被释放,或者它违反程序逻辑并且应该终止程序。

      【讨论】:

        猜你喜欢
        • 2018-03-21
        • 2017-04-04
        • 1970-01-01
        • 1970-01-01
        • 2018-04-26
        • 2011-08-20
        • 2013-08-22
        • 2014-08-07
        • 2021-06-08
        相关资源
        最近更新 更多