Страницы

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

вторник, 26 ноября 2019 г.

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


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


Ответы

Ответ 1



Если тип переменной совпадает с типом возвращаемого значения, то не надо писать 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 выражение - это имя не-volatil объекта с автоматической длительностью хранения (кроме параметра функции или 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, потому что типы разные. }

Ответ 2



Мейерс в "Современном и эффективном C++" дает совет (раздел 5.3, item 26 в оригинале НЕ применять std::move() к локальным объектам, которые могут быть объектом оптимизации возвращаемого значения. Грубо говоря, если это единственный возвращаемый объект (т.е. возвращаемый на всех путях выполнения) - то не стоит. Но если нет - могут быть разные варианты.

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

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