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

发布于 2025-01-09 06:36:38 字数 824 浏览 3 评论 0原文

在以下程序中,结构体 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 限定的,并且处理程序变量是从引用它们的左值初始化的。因此,我们可以预期 A(A&) 构造函数在 catch 中是首选。 Clang 确实这样做了。

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

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

这里是哪个编译器?

In the following program, struct A has both copy-constructor A(const A&) and a constructor from lvalue-reference A(A&). Then an object of A is thrown and then caught as 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 ) {
    }
}

All compilers accept the program.

As far as I understand exception objects are never cv-qualified, and handler variables are initialized from an lvalue that refers to them. So one could expect that A(A&) constructor be preferred in catch. And indeed Clang does so.

But GCC prefers copy constructor printing A(const A&). Demo: https://gcc.godbolt.org/z/1an5M7rWh

Even more weird thing happens in Visual Studio 2019 16.11.7, which prints nothing during program execution.

Which compiler is right here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

手心的温暖 2025-01-16 06:36:38

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

有两个单独的对象

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

int main() {
    尝试 {
        抛出A{};
    }
    捕获(常量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&) 是更好的匹配。海湾合作委员会是错误的。

允许 MSVC 执行复制省略

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

int main() {
  尝试 {
    抛出 C(); // 调用 std::terminate if 构造处理程序
                    // 异常声明对象没有被省略
  } 捕获(C) { }
}

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

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

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

A省略const A 不是同一类型,但它们的区别仅在于简历资格,因此允许复制省略。

In summary, clang and MSVC are both correct. GCC calls the wrong constructor.

There are two separate objects

To understand the required behavior, we must understand that in the following code, there are two objects:

int main() {
    try {
        throw A{};
    }
    catch ( const A ) {
    }
}

Firstly, [except.throw] p3 states

Throwing an exception initializes a temporary object, called the exception object. [...]

Secondly [except.handle] p14.2 explains,

The variable declared by the exception-declaration, of type cv T or cv T&, is initialized from the exception object, of type E, as follows:

  • [...]
  • otherwise, the variable is copy-initialized from an lvalue of type E designating the exception object.

GCC calls the wrong constructor

What happens is similar to:

A temporary = throw A{};
const A a = temporary;

The fact that the variable in the handler is const doesn't affect the cv-qualifications of the temporary object because they are two separate objects.
The temporary object is not const, so A(A&) is a better match during initialization. GCC is wrong.

MSVC is allowed to perform copy elision

Furthermore, it might possible that copy elision is performed.
The standard even has an example of that in [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 confirms that it's allowed, even if cv-qualifications don't match:

[...] copy elision, is permitted in the following circumstances ([...]):

  • [...]
  • when the exception-declaration of an exception handler declares an object of the same type (except for cv-qualification) as the exception object, the copy operation can be omitted by [...]

A and const A are not the same type, but they only differ in cv-qualification, so copy elision is allowed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文