是否有任何版本的C++(甚至是试行标准)是"throw()"并不意味着"cannot throw, ever"?

Was there any version of C++ (even pre-standard) were `throw()` did not mean "cannot throw, ever"?

本文关键字:throw 意味着 ever cannot 版本 任何 C++ 是否 标准      更新时间:2023-10-16

问题是关于C++的历史:ISO标准、修订后的标准(带DR),甚至标准草案;所有这些都算作"一个C++"。

是否存在任何C++,其中不包含属性:

使用空抛出规范throw()声明的函数不能抛出异常

如果该财产不成立,我想举个反例。

评论:

不用说,抛出,然后在函数内部捕获(不重新抛出)异常,会使其成为"抛出异常的函数"。根据函数的规范,抛出异常的函数会向调用方抛出异常。(如果你在内部做一些事情,根据定义,这不是规范的一部分。)

[假设longjmp是被禁止的,因为我们有可破坏的物体。]

这个问题的其他等效表达是:

有没有任何情况下,声明的无抛出函数的调用方必须担心这种可能性(等效属性):

该函数的
  • 将控制权返回给调用方,但不使用返回语句
  • 看到(并能够捕获)该函数引发的异常
  • 调用该函数导致堆栈展开
  • 如果所有其他操作(除了调用该函数)都是非抛出的,则调用方不是非抛出的

换句话说,是否存在编译器无法根据看到被调用函数的无抛出声明来优化调用函数的C++

C++17主要反对将throw作为函数注释。它仍然允许throw(),并认为它等同于noexcept(true)。这意味着throw()表示函数不应该因为异常而退出。违反noexcept(true)保证会导致未定义的行为。

throw()在C++17之前的语义是不同的。过去曾承诺,如果违反throw()子句(即抛出异常并导致函数退出),将调用::std::unexpected()

因此,这意味着在C++17之前,编译器仍然需要有一些机制来检测函数没有从异常中退出,即使它是用throw()注释的。这就是不赞成使用throw(...)函数注释(括号之间有填充物)的原因之一,因为如果不删除throw注释的其他用途,就将throw()更改为等效于noexcept(true)是没有意义的。

编译器资源管理器中的这段代码演示了"意外异常"处理程序。如果您将请求的标准更改为C++17,您将看到该标准的代码消失。

extern void a_different_function();
void test_function() throw ()
{
a_different_function();
}

换句话说,是否存在编译器无法根据看到被调用函数的无抛出声明来优化调用函数的C++?

这个直接问题的答案是否定的。但仅此一点就极具误导性。

编译器对一个恰好调用其他具有throw()声明的函数的函数进行任何类型的优化的能力都非常有限。编译器唯一真正能做的事情就是消除处理调用函数中异常的任何代码的发射。但由于上述代码的性质,只有当它调用的每个函数都不抛出时,它才真正适用。就调用throw()函数的函数的编译器优化而言,基本上就是这样。

今天人们经常谈论noexcept是如何实现优化的。这是真的;明智地使用CCD_ 19可以使对这样的函数进行操作的代码变得更有效率。但重要的是要记住,使用noexcept并不能实现编译器优化;它启用用户代码优化。

让我们以vector<T>的经典案例作为支持noexcept移动的T。这种情况不会更快,因为编译器会看到一系列副本,并自动将它们更改为移动,因为移动构造函数是noexcept。编译器甚至不可能做到这一点;不允许这样重新排列代码,因为这将是一个可检测的更改,这取决于您的复制/移动构造函数的操作。

这种情况变得更快,因为vector的源代码实现检测到T是另一行可移动的。检测到这种情况后,它将调用一个完全不同的代码路径来搅乱vector的元素。调用非抛出函数的代码变得更快是因为调用代码优化了本身,而不是因为编译器检测到任何东西。

简单地说,调用函数的编译器优化从来都不是throw()声明的重点。