Что именно делает std::move с объектом до вызова конструктора или оператора перемещения?
std::move сам по себе не перемещает объект и не изменяет его содержимое. Он лишь выполняет приведение выражения к xvalue — объекту, который можно рассматривать как источник ресурсов для перемещения. Фактическое перемещение происходит позднее, когда выбранный конструктор или оператор присваивания использует такую категорию значения.
В C++11 появились ссылки на rvalue и перемещающие операции. Они позволили передавать ресурсы временных или явно разрешённых к перемещению объектов без дорогостоящего копирования.
Проблема заключалась в том, что именованная переменная даже ссылочного типа является lvalue. Поэтому компилятору нужно было явно сообщить: дальнейшее использование объекта допускает передачу его ресурсов другой операции. Для этого и применяется std::move.
Рассмотрим объект, владеющий динамической памятью, файловым дескриптором или другим ресурсом. Если передать именованный объект обычным способом, перегрузка обычно увидит lvalue и выберет копирование либо вообще не найдёт подходящую операцию.
Ошибочное представление о std::move приводит к двум типичным проблемам: разработчик ожидает перемещения там, где вызывается копирование, или применяет std::move к объекту, который ещё должен использоваться как полноценный владелец ресурса.
std::move — это условное имя для приведения выражения к rvalue-ссылке. В упрощённом виде он снимает ссылочную оболочку с типа и возвращает T&&; никаких деструкторов, конструкторов и операций над самим объектом при этом не вызывается.
В этом примере std::move(text) делает text подходящим аргументом для перемещающего конструктора std::string. Само перемещение происходит внутри создания параметра value, если у типа доступна подходящая перемещающая операция.
После std::move объект не становится уничтоженным и не «перемещается автоматически». Он остаётся существующим объектом, но передаёт выбранной операции право использовать его ресурсы. Поэтому применять std::move к объекту следует только тогда, когда дальнейшее состояние объекта больше не требуется, кроме допустимых операций, определённых его типом.
Приведение не гарантирует перемещение: если перемещающего конструктора нет, он удалён или недоступен, может быть выбрана копирующая операция либо вызов завершится ошибкой компиляции. Кроме того, std::move от const обычно не помогает: результат имеет тип const T&&, а перемещающие операции обычно принимают T&& и не могут изменять const-источник, поэтому выбирается копирование, если оно доступно.
Для универсального кода важно не заменять std::forward на std::move без причины. std::forward сохраняет исходную категорию значения forwarding-ссылки, тогда как std::move безусловно трактует выражение как объект, разрешённый к перемещению.
Сервис собирает крупный объект конфигурации и передаёт его в компонент, который становится единственным владельцем его внутренних буферов. Передача именованной переменной без приведения может вызвать копирование, что увеличит потребление памяти и время обработки.
Вариант с копированием проще и оставляет исходный объект полностью пригодным для дальнейшего использования, но может быть дорогим. Вариант с std::move избегает копирования, однако после передачи нельзя рассчитывать на сохранение исходных ресурсов.
Выбранное решение — применить std::move только в точке последней передачи владения и явно не использовать исходный объект после неё. В результате сохранилась корректность времени жизни, а лишнее копирование больших буферов исчезло. Если же исходный объект должен был использоваться в нескольких местах, правильнее было бы оставить копирование или разделить владение явно, а не перемещать его преждевременно.
std::move операцией времени выполнения?Нет. std::move обычно компилируется в простое приведение типа и не выполняет самостоятельной работы во время выполнения. Затраты и побочные эффекты появляются только у операции, которая будет вызвана после выбора перегрузки: например, у перемещающего конструктора.
std::move иногда не приводит к перемещению?Категория значения — только один из факторов выбора. У типа может отсутствовать подходящий перемещающий конструктор, он может быть удалён, а объект может быть const. В последнем случае результатом является const T&&, и обычный перемещающий конструктор T(T&&) к нему неприменим; при наличии T(const T&) обычно выполняется копирование.
std::move в возвращаемом выражении может быть вреден?Явное приведение локального объекта к rvalue иногда мешает применению гарантированного копирования или перемещения при возврате и может ухудшить оптимизацию. При обычном возврате локального объекта по значению компилятор сам применяет предусмотренные стандартом правила копирования или перемещения. Поэтому std::move в return нужен не по привычке, а только при конкретной причине, которую подтверждает семантика типа и контекст возврата.