Я хочу вернуть из функции большой объект, например переменную 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() к локальным объектам, которые могут быть объектом оптимизации возвращаемого значения.
Грубо говоря, если это единственный возвращаемый объект (т.е. возвращаемый на всех путях выполнения) - то не стоит. Но если нет - могут быть разные варианты.
Комментариев нет:
Отправить комментарий