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

13
在下面的程序中,结构体A同时具有复制构造函数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 ) {
    }
}

所有编译器都接受程序。

据我所知,异常对象从不带有cv限定符,并且处理程序变量是从引用它们的左值初始化的。因此,人们可以期望在catch中优先选择A(A&)构造函数。事实上,Clang确实如此做。

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

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

哪个编译器是正确的?


5
为了允许多态性,您应该通过引用捕获异常,从而避免复制。 - Alan Birtles
有关VS2019的情况,请参阅cppreference复制/移动(自C++11起)可能会受到复制省略的影响 - Adrian Mole
3
这是用于构建异常对象的,不是处理程序中的参数。 - user17732522
看起来维基百科有一个非常类似于你的例子(虽然没有const):https://en.wikipedia.org/wiki/Copy_elision - Jens
1个回答

2
总结一下,clang和MSVC都是正确的。GCC调用了错误的构造函数。
有两个独立的对象
为了理解所需的行为,我们必须明白在以下代码中,有两个对象:
int main() {
    try {
        throw A{};
    }
    catch ( const A ) {
    }
}
首先,[except.throw] p3指出,
引发异常会初始化一个临时对象,称为异常对象。[...]
其次,[except.handle] p14.2解释道,
通过异常声明声明的变量,类型为cv Tcv T&,将从类型为E的异常对象进行初始化,具体如下:
[...]
否则,该变量将从指定异常对象的类型为E的左值进行拷贝初始化。

GCC调用了错误的构造函数

类似于以下情况发生:
A temporary = throw A{};
const A a = temporary;

事实上,在处理程序中的变量是const并不会影响临时对象的cv限定符,因为它们是两个独立的对象。 临时对象并非const,所以在初始化过程中A(A&)更匹配。GCC是错误的。

MSVC允许进行复制省略

此外,可能还会进行复制省略。 标准甚至在[except.throw] p7中给出了一个例子。

int main() {
  try {
    throw C();      // calls std​::​terminate if construction of the handler's
                    // exception-declaration object is not elided
  } catch(C) { }
}

[class.copy.elision] p1.4 确认即使 cv 限定不匹配也是允许的:

[...] 复制消除 在以下情况下被允许([...]):

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

Aconst A 不是同一类型,但它们只在 cv 限定上有所不同,因此允许复制消除。


@HansOlsson 你说得对。我误解了“除了简历资格”的意思。我已经相应地更新了答案。 - Jan Schultke
1
感谢您,已提交GCC错误报告:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104661 - Fedor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接