Что делает функция std::move()


std::move() — это функция из заголовка <utility>, которая не перемещает объект, а преобразует (кастует) его в rvalue-ссылку. Это даёт компилятору понять, что объект можно переместить, а не копировать.

📌 Общее описание

template&lt;typename T&gt;
typename std::remove_reference&lt;T&gt;::type&& move(T&& t) noexcept;
  • std::move() не делает перемещение, а просто разрешает использовать перемещающий конструктор или оператор присваивания, если он определён.

  • Это каст типа: из T в T&& (rvalue-reference).

  • Используется, чтобы оптимизировать работу с ресурсами (например, памятью, файлами и т.д.).

📘 Пример базового использования

std::string str = "Hello";
std::string moved = std::move(str); // вызывает перемещающий конструктор

// теперь str — в "сброшенном" состоянии, но валиден

Здесь std::move(str) превращает str в rvalue-ссылку, и это позволяет вызвать перемещающий конструктор std::string, а не копирующий. Это значительно эффективнее, особенно для больших строк или контейнеров.

🧠 Почему std::move() нужен

В C++ объекты передаются и возвращаются по значению, и компилятор по умолчанию копирует их. Но если вы точно знаете, что объект больше не нужен, вы можете использовать std::move():

  • это позволяет избежать **лишнего копирования
    **
  • и переместить ресурсы (указатели, буферы) из одного объекта в другой

📦 Перемещающий конструктор и оператор присваивания

Чтобы std::move() имел эффект, класс должен определять move-конструктор или move-оператор:

class MyData {
public:
std::vector&lt;int&gt; data;
MyData(std::vector&lt;int&gt; d) : data(std::move(d)) {}
};

Здесь std::move(d) сообщает компилятору: переместить d внутрь data, а не копировать.

📉 Что происходит с объектом после std::move()

Объект остаётся валидным, но находится в неопределённом, пустом или "сброшенном" состоянии. Пример:

std::string s1 = "Hello";
std::string s2 = std::move(s1);
std::cout << s1 << "\\n"; // Пустая строка или неопределённое значение

Важно: использовать s1 после перемещения можно, но его содержимое уже не определено.

🧪 Пример эффективности

std::vector&lt;int&gt; generate() {
std::vector&lt;int&gt; v = {1, 2, 3, 4};
return v; // может сработать NRVO или перемещение
}
std::vector&lt;int&gt; data = generate(); // возможен move вместо copy

std::move() может использоваться внутри компилятора автоматически, но вручную его применяют при передаче/возврате временных объектов, чтобы гарантировать использование move-семантики.

🚫 Что нельзя делать

Ошибка — применять std::move() к объекту, который потом ещё будет использоваться как полноценный:

std::string str = "text"; 
std::string copy = std::move(str); 
std::cout << str; // нельзя рассчитывать на содержимое str

Ошибка логики — std::move() говорит: "я передаю этот объект дальше, больше не использую его".

📦 Применение в std::vector, std::map и других контейнерах

std::vector&lt;std::string&gt; v;
std::string s = "Hello";
v.push_back(std::move(s)); // перемещение, быстрее, чем копирование

Без std::move(s) вызвался бы копирующий конструктор, который может быть дороже, особенно для больших объектов.

🔄 std::move() ≠ std::swap

Важно не путать:

  • std::move() — преобразование в rvalue-ссылку

  • std::swap() — обмен значениями

  • std::move() сам по себе ничего не перемещает — он лишь разрешает это сделать

🧩 Связь с шаблонами и универсальными ссылками

При написании универсальных функций std::move() часто используется, чтобы передать параметр дальше как rvalue:

template &lt;typename T&gt;
void forwardAndConsume(T&& arg) {
consume(std::move(arg)); // перемещение
}

🔒 noexcept

std::move() гарантированно не выбрасывает исключений, так как это просто преобразование ссылки. Функция std::move() сама по себе имеет спецификатор noexcept.

🔎 Использование с типами без move-семантики

Если вы примените std::move() к типу, у которого нет перемещающего конструктора/оператора, произойдёт копирование, а не перемещение. То есть std::move() сам по себе не гарантирует, что произойдёт перемещение.

✅ Пример с пользовательским типом

struct MyClass {
MyClass() {}
MyClass(const MyClass&) { std::cout << "Copy\\n"; }
MyClass(MyClass&&) noexcept { std::cout << "Move\\n"; }
};
MyClass a;
MyClass b = std::move(a); // Вывод: Move

Если убрать MyClass(MyClass&&), то вызовется конструктор копирования.

📜 Резюме по возможностям std::move

Особенность Поведение
Передаёт объект как rvalue Да
--- ---
Сам ничего не перемещает Да
--- ---
Безопасен для повторного использования Частично (объект валиден, но пуст)
--- ---
Требует определения move-семантики Да
--- ---
Гарантирует noexcept Да
--- ---
Используется в оптимизациях Да (особенно для ресурсов)
--- ---
Может привести к ошибкам Да (если использовать перемещённый объект)
--- ---

Таким образом, std::move() — мощный инструмент оптимизации в C++, но он требует ответственного использования: после вызова std::move(obj), программист обязуется больше не использовать содержимое obj, как если бы оно было "передано другому владельцу".