为什么在编译时不强制执行 noexcept

Why noexcept is not enforced at compile time?

本文关键字:强制执行 noexcept 编译 为什么      更新时间:2023-10-16

你可能知道C++11有noexcept 关键字。现在丑陋的部分是这样的:

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

http://en.cppreference.com/w/cpp/language/noexcept_spec

那么这是委员会方面的设计失败,还是他们只是把它留给了编译作者:)从某种意义上说,体面的编译器会强制执行它,坏的编译器仍然可以合规吗?

顺便说一句,如果你问为什么没有第三个选项(也就是不能做),原因是我可以很容易地想到一种(慢)方法来检查函数是否可以抛出。 如果您将输入限制为 5 和 7(也就是我保证文件不会包含 5 和 7 之外的任何内容),并且只有在您给它 33 时才会抛出,那么问题就会偏离轨道, 但恕我直言,这不是一个现实的问题。

委员会非常清楚地考虑了(试图)抛出异常规范不允许的异常的代码将被视为格式错误的可能性,并拒绝了这一想法。根据 $15.4/11:

实现不应仅仅因为表达式在执行时会引发或可能引发包含函数不允许的异常而拒绝表达式。[ 示例:

 extern void f() throw(X, Y);
 void g() throw(X) {
      f(); // OK
  }

f 的调用格式正确,即使在调用时,f可能会引发g不允许的异常Y。 —结束示例 ]

不管是什么促使了这个决定,或者它可能是什么,似乎很明显这不是意外或疏忽的结果。

至于为什么做出这个决定,至少有一些可以追溯到与 C++11 的其他新功能的交互,例如移动语义。

移动语义会使异常安全(尤其是强保证)更难实施/提供。当你进行复制时,如果出现问题,很容易"回滚"事务 - 销毁你制作的任何副本,释放内存,原始文件保持不变。仅当/当复制成功时,您才会销毁原件。

使用移动语义,这更难 - 如果你在移动东西的过程中遇到异常,你已经移动的任何内容都需要移回原来的位置才能恢复到原来的位置 - 但如果移动构造函数或移动赋值运算符可以抛出,你可能会在尝试将东西移以尝试恢复原始对象的过程中得到另一个异常。

结合以下事实:C++11 可以/确实为某些类型自动生成移动构造函数和移动赋值运算符(尽管有一长串限制)。这些不一定保证不会引发异常。如果你显式编写一个移动构造函数,你几乎总是希望确保它不会抛出,这通常甚至很容易做到(因为你通常"窃取"内容,你通常只是复制一些指针 - 很容易做到没有例外)。不过,对于模板来说,它可能会变得更加困难,即使是像std:pair这样的简单模板。一对可以移动的东西和需要复制的东西变得很难很好地处理。

这意味着,如果他们决定在编译时强制执行nothrow(和/或throw()),一些未知(但可能相当大)的代码将被完全破坏 - 多年来一直工作正常的代码突然无法使用新的编译器进行编译。

随之而来的是,尽管它们没有被弃用,但动态异常规范仍保留在语言中,因此无论如何,它们最终都会在运行时至少强制执行一些异常规范。

所以,他们的选择是:

  1. 破坏大量现有代码
  2. 限制移动语义,使其适用于更少的代码
  3. 继续(如 C++03 中所示)以在运行时强制实施异常规范。

我怀疑有人喜欢这些选择中的任何一个,但第三个显然似乎是最后一个坏选择。

一个原因很简单,编译时强制执行异常规范(任何风格)都是一件很痛苦的事情。这意味着,如果添加调试代码,则可能必须重写异常规范的整个层次结构,即使添加的代码不会引发异常也是如此。完成调试后,必须再次重写它们。如果你喜欢这种繁忙的工作,你应该用Java编程。

编译时检查的问题:它实际上不可能以任何有用的方式实现。请参阅下一个示例:

void foo(std::vector<int>& v) noexcept
{
    if (!v.empty())
        ++v.at(0);
}

这段代码可以抛出吗?显然不是。我们可以自动检查吗?没有。Java做这样的事情的方式是把身体放在一个尝试捕获块中,但我不认为它比我们现在拥有的更好......

据我了解(诚然有些模糊),当实际上需要尝试以有用的方式使用它时,发现投掷规范的整个想法是一场噩梦。

调用未指定抛出或不抛出什么的函数必须被视为可能抛出任何内容! 因此,编译器,如果要求你既不抛出也不调用任何可能抛出任何可能抛出你提供的规范之外的东西,实际上强制执行这样的事情,你的代码几乎什么都不能调用,现有的库对你或任何其他试图使用抛出规范的人没有任何用处。

而且由于编译器不可能区分"这个函数可能会抛出一个X,但调用者很可能以这样一种方式调用它,它根本不会抛出任何东西"——人们将永远被这种语言的"特性"所束缚。

所以。。。我相信唯一可能有用的事情是说 nothrow 的想法 - 这表明从 dtors 调用并移动和交换等是安全的,但你正在制作一个符号 - 就像 const 一样 - 更多的是给你的用户一个 API 契约,而不是实际上让编译器负责判断你是否违反了你的契约(就像大多数事情 C/C++ -智能被假定为程序员,而不是保姆编译器)。