在以下程序中,struct
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 中发生了更奇怪的事情,它在程序执行期间不打印任何内容。
这里是哪个编译器?
综上所述,clang 是正确的。 GCC 调用错误的构造函数,MSVC 非法执行复制省略。
要理解所需的行为,我们必须了解以下代码中,有两个对象:
int main() { try { throw A{}; } catch ( const A ) { } }
首先,[ except.throw] p3 声明
抛出异常会初始化一个临时对象,称为异常对象。 [...]
其次 [ except.handle] p14.2 解释,
由 exception-declaration 声明的类型为 cv
或 cvT
的变量是从类型为T&
的异常对象初始化的,如下所示:E
- [...]
- 否则,该变量将从指定异常对象的
类型的左值复制初始化。E
发生的情况类似于:
A temporary = throw A{};
const A a = temporary;
handler中的变量是
const
这一事实不会影响临时对象的cv资格,因为它们是两个独立的对象。
临时对象不是 const
,因此 A(A&)
在初始化期间更匹配。 GCC 错了。
此外,可能会执行复制省略。 该标准甚至在 [ 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
时是不允许的[...] 复制省略,在以下情况下是允许的 ([...]):
- [...]
- 当异常处理程序的Exception-declaration声明一个相同类型的对象(cv-qualification除外)作为异常对象时,可以通过[...]省略复制操作
A
和const A
的简历资格不匹配,因此不允许复制省略。