为什么noexcept在编译时没有强制执行?

NoS*_*tAl 23 c++ c++11

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

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

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

那么这是委员会部分的设计失败还是他们只是把它作为编译作者的练习:)从某种意义上来说,正常的编译器会强制执行它,坏的仍然可以兼容?

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

Jer*_*fin 21

委员会非常清楚地认为,(试图)抛出异常规范不允许的异常的代码被认为是不正确的,并且拒绝了这个想法.根据$ 15.4/11:

实现不应仅仅因为执行时抛出或抛出包含函数不允许的异常而拒绝表达式.[例如:

 extern void f() throw(X, Y);

 void g() throw(X) {
      f(); // OK
  }
Run Code Online (Sandbox Code Playgroud)

该呼叫f很好形成,即使调用时,f可能会抛出异常Yg不允许. - 末端的例子]

无论是什么促使决定,或者它可能是什么,似乎很清楚,这不是由于事故或疏忽造成的.

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

移动语义可以使异常安全(特别是强保证)更难以强制执行/提供.当你进行复制时,如果出现问题,很容易"回滚"交易 - 销毁你制作的任何副本,释放内存,原件保持不变.只有当/当复制成功时,才会销毁原件.

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

将此与C++ 11可以/确实生成移动构造函数并为某些类型自动移动赋值运算符这一事实相结合(尽管存在一长串限制).这些并不一定能保证不会抛出异常.如果您明确地编写了一个移动构造函数,那么您几乎总是希望确保它不会抛出,这通常很容易做到(因为您通常"窃取"内容,您通常只是复制一些指针 - 容易做到没有例外).尽管如此,即使对于简单的模板,它也会变得更加困难std:pair.可以用需要复制的东西移动的一对东西变得难以处理.

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

除此之外,虽然它们没有被弃用,但动态异常规范仍然保留在语言中,因此它们最终会在运行时最终强制执行至少一些异常规范.

所以,他们的选择是:

  1. 打破了很多现有的代码
  2. 限制移动语义,以便它们适用于更少的代码
  3. 继续(如在C++ 03中)以在运行时强制执行异常规范.

我怀疑是否有人喜欢这些选择,但第三个看起来似乎是最后一个坏事.

  • 主要的激励因素是否则会导致大量的虚假警告.函数通常可以抛出但是在已知它不会的上下文中使用."noexcept"的意思是告诉编译器你不会抛出异常 - 大概这是有用的,因为它*不能*自己说明. (9认同)
  • 另外,如果你有类似“if(is_prime(googolplex+37)) throw 0;”的东西,那么编译器会做什么?它无法评估和解决这个问题。包含此代码的函数可能不会抛出异常,但我们怎么知道?我们必须解决停止问题等。所以,也许他们只是希望规范不是不可能的,这是值得深思的,不是吗? (2认同)
  • @Calmarius,“破坏大量现有代码”的问题在于,它会让 C++11 要么全有要么全无。如果您必须在破坏大部分或全部代码库或完全放弃新标准之间做出选择,那么您将被迫坚持使用 C++03,直到您的代码与 C++11 完全兼容。根据代码库的大小和复杂性,这很可能会花费很长时间。实际上,“破坏大量现有代码”选项与“使新标准对许多用户无法使用”同义。 (2认同)

Pet*_*ker 9

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

  • 您不一定"必须重写整个异常规范的层次结构",因为您可以始终(就像在Java中)`try` /`catch`围绕调试代码以各种形状和形式 - 仍然是PITA,尽管如此比你的答案声称的程度要小得多.:) (2认同)

tr3*_*r3w 7

编译时检查的问题:它无法以任何有用的方式实现.见下一个例子:

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

这段代码可以抛出吗?显然不是.我们可以自动检查吗?并不是的.Java这样做的方法是将主体放在try-catch块中,但我不认为它比我们现在拥有的更好......

  • Since std::vector is not thread safe, that would be race condition anyway... (6认同)
  • 事实上,这段代码显然*可以*抛出.`v`是可以在别处同时修改的引用. (5认同)
  • 实际上你可以指定noexcept函数不能调用可能抛出的函数(例如,只能调用其他noexcepts).然后这可以在编译时被阻止,因为有一个可能的执行路径包括`at`,显然可以抛出.当然,指定类似的东西会使现有的代码库无法实现无法使用:P. (3认同)
  • 是的,这就是我所说的"Java的方式". (2认同)