在 C++ 中通过 `const` 值捕获异常。编译器存在分歧

Fed*_*dor 13 c++ initialization exception language-lawyer

在下面的程序中, structA具有复制构造函数A(const A&)和来自左值引用的构造函数A(A&)A然后抛出一个对象,然后将其捕获为const A

#include <iostream>

struct A {
    A() {}
    A(A&) { std::cout << "A(A&) "; }
    A(const A&) { std::cout << "A(const A&) "; }
};

int main() {
    try {
        throw A{};
    }
    catch ( const A ) {
    }
}
Run Code Online (Sandbox Code Playgroud)

所有编译器都接受该程序。

据我了解,异常对象从来都不是 cv 限定的,并且处理程序变量是从引用它们的左值初始化的。因此,人们可以预期A(A&)构造函数是catch. Clang 确实这样做了。

但 GCC 更喜欢复制构造函数打印A(const A&)。演示: https: //gcc.godbolt.org/z/1an5M7rWh

Visual Studio 2019 16.11.7 中发生了更奇怪的事情,它在程序执行期间不打印任何内容。

这里是哪个编译器?

Jan*_*tke 2

综上所述,clang和MSVC都是正确的。GCC 调用了错误的构造函数。

\n

有两个独立的对象

\n

要理解所需的行为,我们必须了解在以下代码中,有两个对象:

\n
\n
int main() {\n    try {\n        throw A{};\n    }\n    catch ( const A ) {\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

首先,[except.throw] p3状态

\n
\n

抛出异常会初始化一个临时对象,称为异常对象。[...]

\n
\n

其次[except.handle] p14.2解释说,

\n
\n

由异常声明声明的类型为cv Tcv 的 变量T&是从类型为 的异常对象初始化的E,如下所示:

\n
    \n
  • [...]
  • \n
  • E否则,该变量是从指定异常对象类型的左值复制初始化的。
  • \n
\n
\n

GCC 调用了错误的构造函数

\n

发生的情况类似于:

\n
A temporary = throw A{};\nconst A a = temporary;\n
Run Code Online (Sandbox Code Playgroud)\n

事实上,处理程序中的变量不会const影响临时对象的 cv 限定,因为它们是两个单独的对象。\n临时对象不是const,因此A(A&)在初始化期间是更好的匹配。海湾合作委员会是错误的。

\n

允许 MSVC 执行复制省略

\n

此外,可能会执行复制省略。\n该标准甚至在[ except.throw] p7中有一个示例:

\n
\n
int main() {\n  try {\n    throw C();      // calls std\xe2\x80\x8b::\xe2\x80\x8bterminate if construction of the handler\'s\n                    // exception-declaration object is not elided\n  } catch(C) { }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

[class.copy.elision] p1.4确认它是允许的,即使简历资格不匹配:

\n
\n

[...]复制省略,在以下情况下是允许的([...]):

\n
    \n
  • [...]
  • \n
  • 当异常处理程序的异常声明声明与异常对象相同类型(cv 限定除外)的对象时,可以通过 [...] 省略复制操作
  • \n
\n
\n

Aconst A不是同一类型,但它们仅在简历限定上有所不同,因此允许复制省略。

\n