Используйте `std::move()` только тогда, когда нужно явно переместить объект в другую область видимости
Причина
Мы перемещаем, а не копируем, чтобы избежать дублирования и повысить производительность.
Перемещение, как правило, оставляет за собой пустой объект (C.64), что может быть неожиданным или даже опасным, поэтому следует избегать перемещения из lvalue (к ним может быть обращение позднее).
Примечания
Перемещение выполняется неявно, когда источник является rvalue (например, значение в операторе return или результат функции), поэтому не усложняйте код излишними явными вызовами move в таких случаях. Вместо этого пишите короткие функции, возвращающие значения, — и возврат функции, и принятие результата вызывающим кодом будут оптимизированы автоматически.
В целом соблюдение рекомендаций данного документа (включая ограничение области видимости переменных, написание коротких функций с возвратом значений, возврат локальных переменных) устраняет большинство потребностей в явном std::move.
Явное move необходимо для перемещения объекта в другую область видимости: прежде всего для передачи в функцию-«поглотитель» (sink), а также в реализациях операций перемещения (конструктор перемещения, оператор перемещающего присваивания) и операций обмена.
Пример (плохой)
void sink(X&& x); // sink принимает владение x
void user()
{
X x;
// ошибка: нельзя привязать lvalue к rvalue-ссылке
sink(x);
// OK: sink принимает содержимое x; после этого x следует считать пустым
sink(std::move(x));
// ...
// вероятно, ошибка
use(x);
}
Обычно std::move() используется в качестве аргумента для параметра типа &&. После этого считайте объект перемещённым (смотрите C.64) и не читайте его состояние, пока не запишете в него новое значение.
void f()
{
string s1 = "supercalifragilisticexpialidocious";
string s2 = s1; // ok, копирование
assert(s1 == "supercalifragilisticexpialidocious"); // ok
// плохо, если нужно продолжать использовать значение s1
string s3 = move(s1);
// плохо, assert скорее всего не выполнится, s1 изменился
assert(s1 == "supercalifragilisticexpialidocious");
}
Пример
void sink(unique_ptr<widget> p); // передать владение p в sink()
void f()
{
auto w = make_unique<widget>();
// ...
sink(std::move(w)); // ok, передаём в sink()
// ...
sink(w); // Ошибка: unique_ptr специально спроектирован так, чтобы его нельзя было скопировать
}
Примечания
std::move() — это приведение к && под другим именем; само по себе оно ничего не перемещает, а лишь помечает именованный объект как кандидата на перемещение. Язык уже знает распространённые случаи, когда объекты можно перемещать — особенно при возврате значений из функций, — поэтому не усложняйте код избыточными std::move().
Никогда не пишите std::move() только потому, что «это эффективнее». В целом не верьте заявлениям об «эффективности» без данных. В целом не усложняйте код без причины. Никогда не применяйте std::move() к константному объекту — это молча превратится в копирование (смотрите пункт 23 в Meyers15).
Пример (плохой)
vector<int> make_vector()
{
vector<int> result;
// ... заполнить result данными
return std::move(result); // плохо; пишите просто "return result;"
}
Никогда не пишите return move(local_variable); — язык уже знает, что переменная является кандидатом на перемещение. Явный move здесь не поможет, а на некоторых компиляторах может навредить, мешая RVO (оптимизации возвращаемого значения) за счёт создания дополнительного псевдонима на локальную переменную.
Пример (плохой)
vector<int> v = std::move(make_vector()); // плохо; std::move здесь полностью избыточен
Никогда не пишите move на возвращаемом значении вида x = move(f());, где f возвращает по значению. Язык уже знает, что возвращаемое значение является временным объектом, из которого можно перемещать.
Пример
void mover(X&& x)
{
call_something(std::move(x)); // ok
call_something(std::forward<X>(x)); // плохо, не используйте std::forward для rvalue-ссылки
call_something(x); // подозрительно, почему не std::move?
}
template<class T>
void forwarder(T&& t)
{
call_something(std::move(t)); // плохо, не используйте std::move для forwarding-ссылки
call_something(std::forward<T>(t)); // ok
call_something(t); // подозрительно, почему не std::forward?
}
Контроль
- Отмечать использование
std::move(x), гдеx— rvalue или язык уже обработает его как rvalue, включаяreturn std::move(local_variable);иstd::move(f())для функции, возвращающей по значению. - Отмечать функции с параметром
S&&, если нет перегрузкиconst S&для обработки lvalue. - Отмечать аргументы, переданные через
std::move, если параметр не является rvalue-ссылкойX&&и тип допускает копирование. - Отмечать применение
std::moveк forwarding-ссылке (T&&, гдеT— параметр шаблона). Используйте вместо этогоstd::forward. - Отмечать применение
std::moveк чему-либо, кроме rvalue-ссылки на неконстантный объект. - Отмечать применение
std::forwardк rvalue-ссылке (X&&, гдеX— непараметрический тип). Используйте вместо этогоstd::move. - Отмечать применение
std::forwardк чему-либо, кроме forwarding-ссылки. - Отмечать ситуации, когда объект потенциально перемещён, а следующая операция — константная; перед ней должна идти неконстантная операция (в идеале присваивание), чтобы сначала сбросить состояние объекта.