Я хочу вернуть из функции большой объект, например переменную 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, потому что типы разные. }