Страницы

Поиск по вопросам

вторник, 2 октября 2018 г.

Надо ли писать return std::move(local_var)?

Я хочу вернуть из функции большой объект, например переменную std::string s; Надо ли писать return std::move(s);?


Ответ

Если тип переменной совпадает с типом возвращаемого значения, то не надо писать T t; return std::move(t);.
Надо писать T f() { T t; return t; }, т.к. в return переменная t считается rvalue. В стандарте C++11 написано:
12.8 Copying and moving class objects [class.copy] p32 Когда критерии для пропуска копирования/перемещения (copy elision, в т.ч. RVO) соблюдены (кроме ограничений на параметры функции), и выражение обозначающее копируемый объект является lvalue, то выбор конструктора происходит так, как если бы это было rvalue.
Одним из случаев пропуска копирования является NRVO:
В return, если функция возвращает класс*, и return выражение - это имя не-volatile объекта с автоматической длительностью хранения (кроме параметра функции или catch), и его тип такой же как у типа возврата.
(* примечание: struct и union - это тоже типы-классы)
В С++14, формулировка немного другая, случай с return рассматривается отдельно от пропуска копирования (RVO):
Когда критерии для пропуска копирования/перемещения соблюдены [...] и выражение в return это lvalue или id-expression (в т.ч. в скобках) которое обознает объект с автоматической длительностью хранения, объявленный в теле или параметрах функции или лямбда-выражения.

Таким образом, начиная с С++11 стандарт гарантирует, то если оптимизатор выключен, и NRVO не будет, то в следующих случаях будет вызван конструктор перемещения, а не копирования:
struct T { T(); T(T&&); T(const T&); };
T f() { T t; return t; // Имя локальной переменной, вызов T::T(T&&) }
T f() { T t; return (t); // Имя локальной переменной в скобках, вызов T::T(T&&) }
T g(T t) { return t; // Имя параметра, вызов T::T(T&&) }
Так как всё что делает std::move(x) - это преобразует выражение в ссылку на rvalue, то нет никакого смысла писать T f() { T t; return move(t); }. (Это также убирает возможность NRVO).
При этом если выражение в return не является именем, то автоматическое преобразование в rvalue не будет работать.
T f1() { struct X { T t; } x; return x.t; // Часть объекта, будет вызов T::T(const T&) // Надо написать std::move(x.t) }
T g1(T* pt) { return *pt; // Не имя локальной переменной или параметра, // Надо написать std::move(*pt) }
Более сложный пример:
T h1() { T a; while (...) { T b; if (...) { return b; // вызов T::T(T&&) } } return a; // вызов T::T(T&&) }
T h2(T a, T b, bool c) { return c ? a : b; // Не имя локальной переменной или параметра, // надо написать std::move(с ? a : b) }
Если тип переменной отличается от типа возвращаемого значения, то правила выше не действуют, и надо писать std::move(t)
struct Base { virtual ~Base() {} }; struct Derived : Base {};
std::unique_ptr f() { std::unique_ptr r; return std::move(r); // Нужен явный move, потому что типы разные. }

Комментариев нет:

Отправить комментарий