保证复制省略并在抛出异常时删除 copy/move 构造函数
Guaranteed copy elision and deleted copy/move constructor when throwing an exception
从 C++17 开始,prvalue 的含义发生了变化,这使得在某些情况下可以保证复制省略。从 cppreference 开始,在这种情况下 copy/move 构造函数不需要存在或可访问。
当抛出异常时,异常对象被复制初始化,copy/move可能会被复制省略。但是是否要求copy/move构造函数此时必须可用?
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 构造函数。变量 a
是 initialized 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 在创建异常对象和激活异常处理程序时都不会保证复制省略。
复制构造函数存在的要求其实很简单——一方面,在某些情况下复制省略是不明智的,包括抛出和捕获异常;另一方面,编译器只被允许执行省略,但没有义务。所以编译器会在案例中使用构造函数。
您的代码未实现案例的事实并不意味着您的类型 (类) 被允许不支持案例(即使编译器可以成功编译它)。这些案例对于该语言仍然有效,并且必须可以在不修改用于异常的类型 (类) 的代码的情况下实现。
从 C++17 开始,prvalue 的含义发生了变化,这使得在某些情况下可以保证复制省略。从 cppreference 开始,在这种情况下 copy/move 构造函数不需要存在或可访问。
当抛出异常时,异常对象被复制初始化,copy/move可能会被复制省略。但是是否要求copy/move构造函数此时必须可用?
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 构造函数。变量 a
是 initialized 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 在创建异常对象和激活异常处理程序时都不会保证复制省略。
复制构造函数存在的要求其实很简单——一方面,在某些情况下复制省略是不明智的,包括抛出和捕获异常;另一方面,编译器只被允许执行省略,但没有义务。所以编译器会在案例中使用构造函数。
您的代码未实现案例的事实并不意味着您的类型 (类) 被允许不支持案例(即使编译器可以成功编译它)。这些案例对于该语言仍然有效,并且必须可以在不修改用于异常的类型 (类) 的代码的情况下实现。