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

时间:2018-07-18 13:02:35

标签: c++ c++17 return-value-optimization

请参见下面的示例,该示例返回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();
}

为什么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...
}

3 个答案:

答案 0 :(得分:27)

因为返回具有自动存储期限的对象的名称被视为返回对象的右值。请注意,仅当return语句中的表达式为(可能带有括号,但不包括括号)名称时才有效,例如return u;return (u);,因此return {u};照常工作,即复制构造函数被调用。

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

  

在以下复制初始化上下文中,可能会使用移动操作来代替复制操作:

     
      
  • 如果return语句([stmt.return])中的表达式是(可能带有括号)id-expression ,该对象使用主体或参数声明中声明的自动存储期限来命名对象,最内层封闭函数或lambda-expression的子句,或
  •   
  • ...
  •   
     

首先执行重载分辨率以选择副本的构造函数,就好像该对象是由右值指定的。

答案 1 :(得分:5)

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

更具体地说,它是 expr-or-braced-init-list [dcl.init]/1 返回语句的

[stmt.return]/2

  

带有任何其他操作数的return语句仅应在   返回类型不是cv void的函数;退货声明   初始化glvalue结果或prvalue结果对象   (显式或隐式)函数通过 copy-initialization 从   操作数

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

  

在以下复制初始化上下文中,可以代替使用移动操作

     
      
  • 如果return语句([stmt.return])中的表达式是一个(可能带括号的)id表达式,该对象使用最里面的封闭函数的主体或参数声明子句中声明的具有自动存储期限的对象进行命名,或者lambda表达式,或
  •   

用通常的语言来说,之所以调用副本而不是移动,是因为大括号初始化列表调用u恰好是左值。

所以,您可能想知道 braced-init-list 是否调用u,即 rvalue ...

return {std::move(u)};

好吧,u移到了一个新的值UserName上,并且复制省略在此之后起作用。

因此,与

一样,这需要一招
return u;

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();
}

打印

CreateUser() :
move 0

CreateUser_listinit() :
copy 0
move 0

CreateUser_listinit_with_copy_elision() :
copy 0

CreateUser_move_listinit_with_copy_elision() :
move 0

答案 2 :(得分:1)

  

返回{ arg1,arg2,... };

copy-list-initialization 。通过复制初始化从初始化列表中初始化(返回)对象,以进行复制列表初始化