Veg*_*eta 5 c++ copy-elision c++14 c++17 c++20
我有以下代码。
#include <iostream>
struct Box {
Box() { std::cout << "constructed at " << this << '\n'; }
Box(Box const&) { puts("copy"); }
Box(Box &&) = delete;
~Box() { std::cout << "destructed at " << this << '\n'; }
};
auto f() {
Box v;
return v; // is it eligible for NVRO?
}
int main() {
auto v = f();
}
Run Code Online (Sandbox Code Playgroud)
上面的代码产生错误。 call to deleted constructor/function in both gcc and clang
但是,如果我更改代码以返回纯右值,则代码有效。
auto f() {
Box v;
return Box(); // is it because of copy elision?
}
Run Code Online (Sandbox Code Playgroud)
为什么会这样?是因为删除移动构造函数吗?如果我将复制和移动构造函数都更改为显式,它也会产生错误吗?
如果标记为已删除,为什么不能简单地使用定义的复制构造函数
编辑:
compiled with -std=c++20 in both gcc and clang, error.
compiled with -std=c++17 gcc, compiles.
compiled with -std=c++17 clang, error.
Run Code Online (Sandbox Code Playgroud)
编辑2:
clang version: 12.0.0
gcc version: 11.1
Run Code Online (Sandbox Code Playgroud)
该程序中有两种不同的潜在错误。
auto v = f();在 C++14 及更低版本中是错误,因为该操作在逻辑上是移动构造,而在 C++17 及更高版本中不是错误,因为它是临时而不是移动构造的具体化。这是 C++17 保证的复制省略功能,与 NRVO 不同。
return v;在所有版本的 C++ 中都是错误,因为它在逻辑上是移动构造,并且构造函数需要存在且可访问。NRVO 大多数时候都会优化构造函数,但 NRVO 不是强制性的,它只是允许的,因此它不能使原本无效的程序变得有效。然而,gcc 并不能捕获这个错误std=c++17。相反,它会退回到复制构造函数。这似乎是一个 gcc 错误。
C++17 不强制要求 NRVO。当操作数是纯右值时,它要求在语句中进行复制省略return,因此在这种情况下不需要存在复制/移动构造函数。这就是为什么return Box();有效。
显然 C++20 标准发生了变化,影响了复制/移动省略(引用草案):
\n\n\n受影响的子条款:[class.copy.elision]
\n
更改:返回隐式可移动实体的函数可能会调用构造函数,该构造函数采用对与返回的表达式不同的类型的右值引用。可以抛出函数和 catch 子句参数使用移动构造函数。
以及给出的例子:
\nstruct base {\n base();\n base(base const &);\nprivate:\n base(base &&);\n};\n\nstruct derived : base {};\n\nbase f(base b) {\n throw b; // error: base(base &&) is private\n derived d;\n return d; // error: base(base &&) is private\n}\nRun Code Online (Sandbox Code Playgroud)\n以及[class.copy.elision]的要点(强调我的):
\n\n\n隐式可移动实体是具有自动存储持续时间的变量,它可以是非易失性对象,也可以是非易失性对象类型的右值引用。在以下复制初始化上下文中,\n在尝试复制操作之前首先考虑移动操作:
\n如果 return ([stmt.return])\nor co_\xc2\xadreturn ([stmt.return.coroutine]) 语句中的表达式是一个(可能\n带括号的)id 表达式,它命名在中声明的隐式可移动实体\n最里面的函数或lambda 表达式的主体或参数声明子句,或者
\n如果 throw 表达式 ([expr.throw]) 的操作数是一个(可能带括号的)nid 表达式,该表达式命名属于不包含最内层复合语句的范围的隐式可移动实体ntry-block 或 function-try-block(如果有),其复合语句或\nctor-initializer 包含 throw 表达式、重载解析以\n选择复制的构造函数或调用的 return_\xc2\xadvalue 重载为首先执行时就好像表达式或操作数是\nrvalue。如果第一次重载决策失败或未执行,\n则再次执行重载决策,并将表达式 or\n操作数视为左值。
\n[注 3 :无论是否发生复制省略,都会执行此两阶段重载决策。如果未执行省略,它会确定要调用的构造函数或\nreturn_\xc2\xadvalue 重载,并且\n所选的构造函数或\nreturn_\xc2\xadvalue 重载必须是可访问的,\n即使调用已被省略。\xe2\x80\x94尾注]
\n