Почему возвращение 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
перемещение?
Здесь соответствующий образец 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...
}
Ответы
Ответ 1
Поскольку возврат имени объекта с автоматической продолжительностью хранения рассматривается как возвращающее значение r объекта. Обратите внимание, что это работает, только если выражение в операторе return является (возможно, в скобках, не включая фигурные скобки), например return u;
или return (u);
, поэтому return {u};
работает как обычно, т.е. вызывается конструктор копирования.
Связанная с этим часть в стандарте [class.copy.elision]/3:
В следующих контекстах копирования-инициализации вместо операции копирования может использоваться операция перемещения:
- Если выражение в операторе return ([stmt.return]) является (возможно, в скобках) id-выражением, которое называет объект с автоматическим временем хранения, объявленным в теле или параметром-объявлением-предложением самой внутренней охватывающей функции или лямбда-выражения, или же
- ...
разрешение перегрузки для выбора конструктора для копии сначала выполняется так, как если бы объект был обозначен rvalue.
Ответ 2
Это своего рода бит-init-список. [dcl.init.list]/1.3
Чтобы быть более конкретным, это " expr-or-braced-init-list [dcl.init]/1
заявления о возврате " [stmt.return]/2
Оператор return с любым другим операндом должен использоваться только в функции, тип возврата которой не является vv void; оператор return инициализирует результат glvalue или объект результата prvalue (явный или неявный) вызов функции путем инициализации копии из операнда.
С этого момента позвольте мне ответить xskxzr, что упоминание [class.copy.elision]/3
В следующих контекстах копирования-инициализации вместо операции копирования может использоваться операция перемещения:
- Если выражение в операторе return ([stmt.return]) является (возможно, в скобках) id-выражением, которое называет объект с автоматическим временем хранения, объявленным в теле или параметром-объявлением-предложением самой внутренней охватывающей функции или лямбда-выражения, или же
В нормальных человеческих словах причина, что копия вызывается вместо перемещения, потому что бит-init-list вызывает u
который оказался lvalue.
Таким образом, вы можете знать, если бит-init-list вызывает u
то есть rvalue...
return {std::move(u)};
Ну, u
перемещается в новое значение UserName
и копирует работу elision сразу после.
Таким образом, это занимает один ход, как в
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
Ответ 3
return {arg1, arg2,...};
инициализация списка копий. объект (return) инициализируется из списка инициализаторов путем инициализации копирования для инициализации списка копий