Зачем нужен std::move, если он сам не перемещает объект?
std::move не перемещает объект и не изменяет его состояние напрямую. Он лишь преобразует выражение в xvalue, сообщая перегрузке функции или конструктору, что объект можно рассматривать как источник для перемещения.
Само перемещение выполняется выбранным move-конструктором, move-присваиванием или другой функцией, принимающей rvalue-ссылку. После перемещения исходный объект остаётся существующим и пригодным для разрушения, но его значение обычно не следует использовать без предварительного присваивания нового значения.
До появления семантики перемещения передача временных объектов и передача владения ресурсами часто требовали копирования. Для объектов, владеющих памятью, файловыми дескрипторами или другими ресурсами, такая копия могла быть дорогой либо запрещённой.
C++11 ввёл rvalue-ссылки и связанные с ними правила перегрузки, чтобы ресурс можно было передать новому объекту без копирования самого ресурса. std::move предоставляет явный способ выбрать этот путь для именованного объекта.
Именованная переменная является lvalue, даже если её тип допускает перемещение. Поэтому обычная передача такой переменной в функцию обычно выбирает копирующую перегрузку, если она доступна.
Без явного указания на возможность перемещения программа может выполнить дорогую копию. Если же применить std::move без понимания последствий, исходный объект можно оставить в изменённом состоянии и ошибочно продолжить использовать его как будто он сохранил прежнее значение.
Вызов std::move — это условное приведение выражения к типу rvalue-ссылки. На практике он позволяет механизму разрешения перегрузки предпочесть конструктор или оператор присваивания перемещения.
В примере std::move(source) не переносит символы сам по себе. Конструктор std::string, получивший rvalue, может забрать внутренний буфер source вместо копирования его содержимого. Конкретное состояние source после операции зависит от типа; для объекта стандартной библиотеки оно обычно остаётся допустимым, но его значение может быть неопределённым в разрешённом стандартом смысле.
Важно отличать состояние объекта от его времени жизни. Перемещение не уничтожает исходный объект и не превращает его в недействительную ссылку. Объект можно уничтожить, присвоить ему новое значение или вызвать операции, для которых тип гарантирует корректность состояния после перемещения.
std::move не гарантирует, что перемещение вообще произойдёт. Если у типа нет подходящей операции перемещения, может быть выбрана копирующая операция; кроме того, сам тип может реализовать перемещение фактически как копирование. Для const-объекта перемещение часто невозможно эффективно: операция перемещения обычно принимает неконстантную rvalue-ссылку, поскольку должна изменять источник.
Не следует без причины применять std::move к возвращаемому локальному объекту. Современный C++ умеет выполнять гарантированное или допускаемое устранение копирования при возврате, а явное перемещение иногда мешает этому и ухудшает оптимизацию.
Сервис формирует крупный буфер результата и передаёт его в очередь задач, которая должна получить владение данными. Рассматривались три варианта: передать буфер по константной ссылке, скопировать его в очередь или явно переместить.
Передача по константной ссылке не определяет, кто и как будет владеть данными после выхода из функции. Копирование проще для рассуждений, но увеличивает время и пиковое потребление памяти. Перемещение снижает стоимость передачи, однако после отправки исходный буфер нельзя считать содержащим прежний результат.
Выбрали перемещение, потому что исходный буфер после постановки задачи больше не требовался, а контракт очереди явно фиксировал передачу владения. После операции исходную переменную либо сразу переиспользовали через присваивание нового значения, либо не использовали до её уничтожения; это исключило зависимость от конкретного состояния объекта после перемещения.
Нет. Это преобразование выражения, а не операция над объектом. Изменение появляется только если последующий вызов выбранной функции действительно перемещает ресурс из источника.
Да, но только в пределах гарантий конкретного типа. Объект остаётся валидным, однако его прежнее значение не гарантируется. Надёжные действия обычно включают уничтожение, присваивание нового значения и вызовы операций, для которых документация типа допускает состояние после перемещения.
Перемещение требует доступной подходящей операции и обычно эффективно, когда ресурс можно передать простым обменом владельца. Для небольших объектов копирование может быть не дороже, а для const-объекта выбор перемещающей перегрузки часто невозможен. Кроме того, тип вправе реализовать move-операцию без существенной оптимизации, поэтому само наличие std::move не является гарантией выигрыша производительности.