为什么返回 std::optional 有时会移动有时会复制?
Why does returning a std::optional sometimes move and sometimes copy?
参见下面返回可选的 UserName
的示例 - a movable/copyable class.
std::optional<UserName> CreateUser()
{
UserName u;
return {u}; // this one will cause a copy of UserName
return u; // this one moves UserName
}
int main()
{
auto d = CreateUser();
}
为什么 return {u}
导致复制并 return u
移动?
这是相关的大肠杆菌样本:http://coliru.stacked-crooked.com/a/6bf853750b38d110
另一种情况(感谢@Slava的评论):
std::unique_ptr<int> foo()
{
std::unique_ptr<int> p;
return {p}; // uses copy of unique_ptr and so it breaks...
}
因为 returning 具有自动存储持续时间的对象的 name 被视为 returning 对象的右值。请注意,仅当 return 语句中的表达式是一个(可能带括号,不包括大括号)名称时才有效,例如 return u;
或 return (u);
,因此 return {u};
照常工作,即调用复制构造函数。
标准中的相关部分[class.copy.elision]/3:
In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
- If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
- ...
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
return { arg1, arg2, ... } ;
是copy-list-initialization。 (return) 对象通过复制列表初始化的复制初始化从初始化列表初始化
这是一种 braced-init-list。 [dcl.init.list]/1.3
更具体地说,它是一个 " expr-or-braced-init-list [dcl.init]/1
return 语句 " [stmt.return]/2
A return statement with any other operand shall be used only in a
function whose return type is not cv void; the return statement
initializes the glvalue result or prvalue result object of the
(explicit or implicit) function call by copy-initialization from the
operand.
从这点来说,让我引用xskxzr提到[class.copy.elision]/3
的回答
In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
- If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
用普通人的话来说,调用copy而不是move的原因是braced-init-list调用u
恰好是左值。
所以,你可能想知道 braced-init-list 是否调用 u
即 rvalue ...
return {std::move(u)};
好吧,u
被移动到一个新的右值 UserName
并且复制省略立即生效。
所以这需要一步
return u;
wandbox.org/permlink/7u1cPc0TG9gqToZD
#include <iostream>
#include <optional>
struct UserName
{
int x;
UserName() : x(0) {};
UserName(const UserName& other) : x(other.x) { std::cout << "copy " << x << "\n"; };
UserName(UserName&& other) : x(other.x) { std::cout << "move " << x << "\n"; };
};
std::optional<UserName> CreateUser()
{
UserName u;
return u; // this one moves UserName
}
std::optional<UserName> CreateUser_listinit()
{
UserName u;
auto whatever{u};
return whatever;
}
std::optional<UserName> CreateUser_listinit_with_copy_elision()
{
UserName u;
return {u};
}
std::optional<UserName> CreateUser_move_listinit_with_copy_elision()
{
UserName u;
return {std::move(u)};
}
int main()
{
std::cout << "CreateUser() :\n";
[[maybe_unused]] auto d = CreateUser();
std::cout << "\nCreateUser_listinit() :\n";
[[maybe_unused]] auto e = CreateUser_listinit();
std::cout << "\nCreateUser_listinit_with_copy_elision() :\n";
[[maybe_unused]] auto f = CreateUser_listinit_with_copy_elision();
std::cout << "\nCreateUser_move_listinit_with_copy_elision() :\n";
[[maybe_unused]] auto g = CreateUser_move_listinit_with_copy_elision();
}
打印
CreateUser() :
move 0
CreateUser_listinit() :
copy 0
move 0
CreateUser_listinit_with_copy_elision() :
copy 0
CreateUser_move_listinit_with_copy_elision() :
move 0
参见下面返回可选的 UserName
的示例 - a movable/copyable class.
std::optional<UserName> CreateUser()
{
UserName u;
return {u}; // this one will cause a copy of UserName
return u; // this one moves UserName
}
int main()
{
auto d = CreateUser();
}
为什么 return {u}
导致复制并 return u
移动?
这是相关的大肠杆菌样本:http://coliru.stacked-crooked.com/a/6bf853750b38d110
另一种情况(感谢@Slava的评论):
std::unique_ptr<int> foo()
{
std::unique_ptr<int> p;
return {p}; // uses copy of unique_ptr and so it breaks...
}
因为 returning 具有自动存储持续时间的对象的 name 被视为 returning 对象的右值。请注意,仅当 return 语句中的表达式是一个(可能带括号,不包括大括号)名称时才有效,例如 return u;
或 return (u);
,因此 return {u};
照常工作,即调用复制构造函数。
标准中的相关部分[class.copy.elision]/3:
In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
- If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
- ...
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
return { arg1, arg2, ... } ;
是copy-list-initialization。 (return) 对象通过复制列表初始化的复制初始化从初始化列表初始化
这是一种 braced-init-list。 [dcl.init.list]/1.3
更具体地说,它是一个 " expr-or-braced-init-list [dcl.init]/1
return 语句 " [stmt.return]/2
A return statement with any other operand shall be used only in a function whose return type is not cv void; the return statement initializes the glvalue result or prvalue result object of the (explicit or implicit) function call by copy-initialization from the operand.
从这点来说,让我引用xskxzr提到[class.copy.elision]/3
的回答In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
- If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
用普通人的话来说,调用copy而不是move的原因是braced-init-list调用u
恰好是左值。
所以,你可能想知道 braced-init-list 是否调用 u
即 rvalue ...
return {std::move(u)};
好吧,u
被移动到一个新的右值 UserName
并且复制省略立即生效。
所以这需要一步
return u;
wandbox.org/permlink/7u1cPc0TG9gqToZD
#include <iostream>
#include <optional>
struct UserName
{
int x;
UserName() : x(0) {};
UserName(const UserName& other) : x(other.x) { std::cout << "copy " << x << "\n"; };
UserName(UserName&& other) : x(other.x) { std::cout << "move " << x << "\n"; };
};
std::optional<UserName> CreateUser()
{
UserName u;
return u; // this one moves UserName
}
std::optional<UserName> CreateUser_listinit()
{
UserName u;
auto whatever{u};
return whatever;
}
std::optional<UserName> CreateUser_listinit_with_copy_elision()
{
UserName u;
return {u};
}
std::optional<UserName> CreateUser_move_listinit_with_copy_elision()
{
UserName u;
return {std::move(u)};
}
int main()
{
std::cout << "CreateUser() :\n";
[[maybe_unused]] auto d = CreateUser();
std::cout << "\nCreateUser_listinit() :\n";
[[maybe_unused]] auto e = CreateUser_listinit();
std::cout << "\nCreateUser_listinit_with_copy_elision() :\n";
[[maybe_unused]] auto f = CreateUser_listinit_with_copy_elision();
std::cout << "\nCreateUser_move_listinit_with_copy_elision() :\n";
[[maybe_unused]] auto g = CreateUser_move_listinit_with_copy_elision();
}
打印
CreateUser() :
move 0
CreateUser_listinit() :
copy 0
move 0
CreateUser_listinit_with_copy_elision() :
copy 0
CreateUser_move_listinit_with_copy_elision() :
move 0