Почему возврат std::Optional иногда перемещает, а иногда копирует?

См. приведенный ниже пример возврата необязательного параметра 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...
}

person fen    schedule 18.07.2018    source источник
comment
Вы забыли удалить несвязанный с вопросом оператор new и delete в примере.   -  person Slava    schedule 18.07.2018
comment
@Слава удалил удаление/новые перегрузки   -  person fen    schedule 18.07.2018
comment
Более простой код для воспроизведения: std::unique_ptr<int> foo() { std::unique_ptr<int> p; return {p}; }   -  person Slava    schedule 18.07.2018
comment
спасибо @Slava - я обновил вопрос   -  person fen    schedule 18.07.2018


Ответы (3)


Потому что возврат имени объекта с автоматическим сроком хранения рассматривается как возврат rvalue объекта. Обратите внимание, что это работает только в том случае, если выражение в операторе return представляет собой имя (возможно, в скобках, без фигурных скобок), например return u; или return (u);, поэтому return {u}; работает как обычно, т. е. вызывается конструктор копирования.

Связанная часть в стандарте [class.copy.elision]/3:

В следующих контекстах инициализации копирования вместо операции копирования может использоваться операция перемещения:

  • Если выражение в операторе возврата ([stmt.return]) является (возможно, заключенным в скобки) id-выражением, которое называет объект с длительностью автоматического хранения, объявленной в теле или предложении объявления параметра самая внутренняя объемлющая функция или лямбда-выражение, или
  • ...

разрешение перегрузки для выбора конструктора для копии сначала выполняется, как если бы объект был обозначен rvalue.

person xskxzr    schedule 18.07.2018
comment
Я не понимаю, почему return{u} не получил такой же обработки, как и другие, я имею в виду, что это оператор возврата, переменная u выходит за рамки, есть ли случай, когда следует использовать COPY? - person watashiSHUN; 11.12.2020
comment
@watashiSHUN Я не знаю. Это правило. Я думаю, что только комитет, который установил это правило, знает причину. - person xskxzr; 14.12.2020

Это своего рода список инициализации в фигурных скобках. [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. исключение]/3

В следующих контекстах инициализации копирования операция перемещения может использоваться вместо операции копирования:

  • Если выражение в операторе возврата ([stmt.return]) является (возможно, заключенным в скобки) id-выражением, которое называет объект с автоматическим сроком хранения, объявленным в теле или предложении-объявления-параметра самой внутренней охватывающей функции или лямбда-выражения , или же

Говоря обычными человеческими словами, причина, по которой вместо перемещения вызывается копирование, заключается в том, что в фигурном списке инициализации вызывается u, который оказался lvalue.

Итак, вы можете знать, вызывает ли braced-init-list u то есть rvalue...

return {std::move(u)};

Что ж, u перемещается на новое значение rvalue 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
person sandthorn    schedule 18.07.2018

return { arg1, arg2, ... } ;

это copy-list-initialization. (возвратный) объект инициализируется из списка инициализаторов путем инициализации копирования для инициализации списка копирования

person Zang MingJie    schedule 18.07.2018
comment
но return u; также является инициализацией копирования, как уже упоминалось инициализация копирования - cppreference.com - person fen; 18.07.2018
comment
@fen Инициализация copy не совпадает с инициализацией copy list - person Caleth; 18.07.2018
comment
На самом деле это не так. ответить на вопрос. Да, return {u}; — это инициализация списка копирования. Истинный. Однако почему этот копирует, пока другой перемещается? - person Barry; 18.07.2018