保证复制省略并在抛出异常时删除 copy/move 构造函数

Guaranteed copy elision and deleted copy/move constructor when throwing an exception

从 C++17 开始,prvalue 的含义发生了变化,这使得在某些情况下可以保证复制省略。从 cppreference 开始,在这种情况下 copy/move 构造函数不需要存在或可访问。

当抛出异常时,异常对象被复制初始化,copy/move可能会被复制省略。但是是否要求copy/move构造函数此时必须可用?

来自[except.throw]

When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided ([class.copy.elision]). The destructor is potentially invoked ([class.dtor]).

标准中提到相应的构造函数必须是非删除且可访问的,即使它被省略了。

但是我测试发现GCC和Clang都允许在删除相应的构造函数时抛出:

struct A {
    A() = default;
    A(A&&) = delete;
};

try {
    throw A{};    // copy elision
} catch(...) {}

代码编译通过,但似乎不符合标准要求。如果我把版本从C++17降到C++14,他们都会报错:

struct A {
    A() = default;
    A(A&&) = delete;
};

try {
    throw A{};    // error: call to deleted constructor of 'A'
} catch(...) {}

是我对标准的理解有误,还是编译器放宽了限制?

就您对该段的分析实际适用而言,您对标准的理解是正确的。

但它不适用,因为没有构造函数被考虑用于复制初始化。

保证省略有点用词不当;这是对概念的有用解释,但它并没有准确反映它 工作原理 就标准而言。 .

保证省略有效

A a = A{}; 是复制初始化,但它甚至 假设 调用 copy/move 构造函数。变量 ainitialized by the prvalue's initializer:

The result of a prvalue is the value that the expression stores into its context. A prvalue whose result is the value V is sometimes said to have or name the value V. The result object of a prvalue is the object initialized by the prvalue

a是由纯右值初始化的“结果对象”。

这里也是一样。 A{} 是纯右值。异常对象是纯右值要初始化的“结果对象”。没有临时对象,没有考虑使用的 copy/move 构造函数。

首先,C++17 在创建异常对象和激活异常处理程序时都不会保证复制省略。

复制构造函数存在的要求其实很简单——一方面,在某些情况下复制省略是不明智的,包括抛出和捕获异常;另一方面,编译器只被允许执行省略,但没有义务。所以编译器会在案例中使用构造函数。

您的代码未实现案例的事实并不意味着您的类型 (类) 被允许不支持案例(即使编译器可以成功编译它)。这些案例对于该语言仍然有效,并且必须可以在不修改用于异常的类型 (类) 的代码的情况下实现。