为什么返回std :: optional有时会移动并有时复制?

fen*_*fen 27 c++ return-value-optimization c++17

请参阅以下返回可选项的示例UserName- 可移动/可复制类.

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();
}
Run Code Online (Sandbox Code Playgroud)

为什么要return {u}复制和return u移动?

这是相关的coliru样本: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...
}
Run Code Online (Sandbox Code Playgroud)

xsk*_*xzr 27

因为返回具有自动存储持续时间的对象的名称被视为返回对象的右值.请注意,这仅适用于return语句中的表达式(可能是括号,不包括大括号)名称,return u;或者return (u);,因此return {u};通常工作,即调用复制构造函数.

标准[class.copy.elision]/3中的相关部分:

在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:

  • 如果return语句([stmt.return])中的表达式是一个(可能带括号的)id-expression,它指定一个对象,该对象具有在最内层封闭函数或lambda-expression的body或parameter-declaration-clause中声明的自动存储持续时间, 要么
  • ...

首先执行重载决策以选择副本的构造函数,就好像该对象是由rvalue指定的一样.


san*_*orn 6

这是一种braced-init-list.[dcl.init.list] /1.3

更具体地说,它是一个" expr-or- braced -init-list [dcl.init]/1

一个的return语句 " [stmt.return]/2

带有任何其他操作数的return语句只能在返回类型不是cv void的函数中使用; return语句初始化glvalue结果或(显性或隐性)函数调用的prvalue结果对象副本初始化操作数.

从这一点开始,让我提一下xskxzr的答案,提到[class.copy.elision]/3

在以下复制初始化上下文中,可能会使用移动操作而不是复制操作:

  • 如果return语句([stmt.return])中的表达式是一个(可能带括号的)id-expression,它指定一个对象,该对象具有在最内层封闭函数或lambda-expression的body或parameter-declaration-clause中声明的自动存储持续时间, 要么

在正常的人类话语中,由于braced-init-list调用u碰巧是左值,因此调用副本而不是移动的原因.

所以,你可能想知道,如果支撑-初始化列表调用u右值 ...

return {std::move(u)};
Run Code Online (Sandbox Code Playgroud)

嗯,之后u被转移到一个新的rvalue UserName和复制elision工作.

所以这需要一举一动

return u;
Run Code Online (Sandbox Code Playgroud)

godbolt.org/g/b6stLr

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();
}
Run Code Online (Sandbox Code Playgroud)

打印

CreateUser() :
move 0

CreateUser_listinit() :
copy 0
move 0

CreateUser_listinit_with_copy_elision() :
copy 0

CreateUser_move_listinit_with_copy_elision() :
move 0
Run Code Online (Sandbox Code Playgroud)