Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работать с std::move в C++
Определение
std::move — это функция, которая преобразует lvalue в rvalue. Это позволяет компилятору понять, что значение больше не нужно, и он может переместить его ресурсы вместо копирования.
Проблема, которую решает
Представим класс с дорогим ресурсом:
class String {
private:
char* data;
size_t length;
public:
// Копирующий конструктор (дорого)
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
std::cout << "Скопировали\n";
}
// Перемещающий конструктор (дешево)
String(String&& other) noexcept {
data = other.data; // Просто копируем указатель
length = other.length;
other.data = nullptr; // Обнуляем старый
other.length = 0;
std::cout << "Переместили\n";
}
~String() {
delete[] data;
}
};
// БЕЗ std::move
String createString() {
String s("hello");
return s; // Что произойдёт?
}
String str = createString();
// Может быть копирование (дорого)
// Или RVO (Return Value Optimization) спасёт
Задача: как явно сказать компилятору "используй перемещение вместо копирования"?
Решение: std::move
String a("hello");
String b = a; // Копирование (нужна копия)
String c = std::move(a); // Перемещение (a больше не нужна)
std::cout << a.data() << std::endl; // Пусто! (a была перемещена)
std::cout << c.data() << std::endl; // "hello"
// Вывод:
// Переместили
Что делает std::move?
Это просто преобразование типа:
template <typename T>
std::remove_reference_t<T>&& move(T&& arg) noexcept {
return static_cast<std::remove_reference_t<T>&&>(arg);
}
// Вот и всё! Просто static_cast к rvalue reference
Это не перемещает данные — это просто говорит: "это значение можно перемещать".
Практический пример
class Vector {
private:
int* data;
size_t size;
public:
// Копирующий конструктор
Vector(const Vector& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
std::cout << "Копирование (дорого для большого массива)\n";
}
// Перемещающий конструктор
Vector(Vector&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
std::cout << "Перемещение (дешево)\n";
}
~Vector() {
delete[] data;
}
};
int main() {
Vector v1(1000000); // Большой вектор
Vector v2 = v1; // Копирование — медленно
Vector v3 = std::move(v1); // Перемещение — быстро
// v1 теперь пуста (data = nullptr)
// v3 содержит данные v1
}
// Вывод:
// Копирование (дорого для большого массива)
// Перемещение (дешево)
Использование в функциях
Возврат из функции:
Vector createVector(size_t size) {
Vector v(size);
// ... инициализация
return std::move(v); // Явное перемещение
// Хотя RVO обычно срабатывает и без этого
}
Передача в функцию:
void processVector(Vector v) { // Копирование параметра
// ...
}
Vector myVec(100);
processVector(std::move(myVec)); // Перемещаем вместо копирования
Перемещение контейнеров
std::vector<Vector> vectors;
Vector v(1000);
// БЕЗ std::move — копирование
vectors.push_back(v);
// СО std::move — перемещение
vectors.push_back(std::move(v));
// После push_back(std::move(v)):
// v пусто, а vectors[0] содержит данные
std::move в цикле
std::vector<std::string> strings;
strings.push_back("hello");
strings.push_back("world");
std::vector<std::string> result;
// Копирование (медленно)
for (const auto& s : strings) {
result.push_back(s);
}
// Перемещение (быстро)
for (auto& s : strings) {
result.push_back(std::move(s));
}
ВАЖНО: After move, объект в валидном состоянии
После std::move объект должен быть в валидном состоянии, но его значение неопределено:
std::string s = "hello";
std::string moved = std::move(s);
std::cout << s << std::endl; // Неопределённо (может быть пусто)
std::cout << moved << std::endl; // "hello"
// Можно ещё использовать s, но его значение неизвестно
s = "new value"; // ✅ Можно присвоить новое значение
std::cout << s << std::endl; // "new value"
Move semantics в std библиотеке
std::vector:
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1);
// v1 становится пустым, v2 имеет {1, 2, 3}
std::unique_ptr:
std::unique_ptr<int> p1(new int(42));
std::unique_ptr<int> p2 = std::move(p1);
// p1 становится nullptr, p2 имеет указатель
std::string:
std::string s1 = "hello world";
std::string s2 = std::move(s1);
// s1 может быть пустой, s2 имеет "hello world"
Когда использовать std::move?
✅ Используй:
- Когда переменная больше не нужна:
Vector v = createVector();
storage.push_back(std::move(v)); // v больше не нужна
- В возвращаемом значении (хотя RVO часто обработает):
std::vector<int> getVector() {
std::vector<int> result = {1, 2, 3};
return std::move(result);
}
- При передаче в функцию, которая берёт rvalue reference:
void process(std::vector<int>&& v) { }
std::vector<int> vec = {1, 2, 3};
process(std::move(vec));
❌ Не используй:
- С переменными, которые ещё нужны:
Vector v = getVector();
storage.push_back(std::move(v));
processVector(v); // ❌ BAD: v пусто
- В параметрах по значению (уже скопируется):
void foo(Vector v) { } // v — копия
Vector v = getVector();
foo(std::move(v)); // std::move не поможет, всё равно копируется
- В return statement без необходимости (RVO обработает):
Vector getVector() {
Vector v;
// ...
return v; // RVO скопирует вместо копирования
return std::move(v); // Не нужно
}
Производительность
// Копирование: O(n)
Vector v1(1000000);
Vector v2 = v1; // Копируем 1000000 элементов
// Перемещение: O(1)
Vector v3 = std::move(v1); // Просто копируем указатель
Для больших объектов разница может быть 100x+.
std::move vs std::forward
std::move:
- Безусловно преобразует в rvalue
- Используется когда уверен, что больше не нужно значение
std::forward:
- Условно преобразует (сохраняет категорию)
- Используется в template функциях для ideal forwarding
template <typename T>
void wrapper(T&& arg) {
std::forward<T>(arg); // Сохраняет категорию
std::move(arg); // Всегда rvalue
}
Заключение
- std::move преобразует lvalue в rvalue
- Позволяет использовать перемещающий конструктор вместо копирующего
- Zero-cost abstraction — только static_cast, нет overhead
- Critical для производительности с большими объектами
- Используй когда уверен, что значение больше не нужно
- После move объект валиден, но в неопределённом состоянии