Move-семантика — передача владения вместо копирования
До C++11 передача объекта означала его копирование: возврат std::vector из функции — копия, вставка строки в контейнер — копия. Каждая копия выделяет память, проходит байты, освобождает источник. Move-семантика решает это: если источник — временный или объект явно «разрешил забрать ресурс», получатель переносит владение. Для std::vector это перенос указателя — O(1) вместо O(n).
В C++ move работает на стыке нескольких механизмов: новой категории значений (xvalue), нового вида ссылок (T&&), std::move как cast, move-конструктора и move-оператора, noexcept-контракта, copy elision, forwarding references в шаблонах. Ловушки: std::move от const, именованный T&& как lvalue, return std::move(v) блокирующий NRVO, незаявленный noexcept отключающий move в std::vector.
Карта темы
- Категории значений — lvalue, rvalue, prvalue, xvalue, glvalue — что и почему можно переместить.
- Rvalue-ссылки —
T&&, правило именованного параметра, reference collapsing. - std::move и moved-from состояние —
std::moveкак cast; valid-but-unspecified; ловушка сconst. - Move-конструктор и Rule of Five —
T(T&&) noexcept, правило подавления, Rule of Zero/Five. - Perfect forwarding —
std::forward, forwarding reference,std::movevsstd::forward. - Copy elision (RVO/NRVO) — когда move не нужен; почему
return std::move(v)хужеreturn v. - Move-only типы — уникальное владение через
= deletecopy:std::unique_ptr,std::thread.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
std::move от const-объекта | Тихо выбирается copy-ctor, перемещения не происходит, без warnings |
Использование объекта после std::move | Чтение valid-but-unspecified-состояния → непредсказуемые значения |
Move-конструктор без noexcept | std::vector при realloc копирует вместо move — выигрыш от move потерян |
return std::move(v) для локальной переменной | Блокирует NRVO: гарантированный move вместо «вообще ничего» |
Использование b как rvalue после void f(T&& b) | b — lvalue-выражение; нужен std::move(b) или std::forward<T>(b) |
std::forward<T> вне шаблонной функции | Всегда даёт rvalue — лучше написать std::move напрямую |
| Объявить только move-ctor, ожидая работающий copy | Copy неявно удалён — компилятор выдаст ошибку только в месте использования |
| Объявить пользовательский деструктор, ожидать сгенерированный move | Move подавлен; принудительный move молча превращается в copy |
Значение для собеседований
Move-семантика — один из самых частых топиков C++ на уровне middle и выше. Интервьюер проверяет не синтаксис, а понимание почему механизм работает именно так.
Типичный неправильный ответ: «std::move перемещает объект». Нет, это cast к rvalue-ссылке. Перемещает move-конструктор, выбранный перегрузкой.
Что проверяет интервьювер:
- Понимание категорий значений — lvalue/prvalue/xvalue/glvalue.
- Почему именованный
T&&в теле функции — lvalue-выражение. std::movevsstd::forward— когда и почему.- Состояние объекта после
std::move: valid but unspecified. - Функциональная роль
noexceptна move (std::vectorrealloc). - RVO/NRVO и почему
return std::move(v)для локалки — хуже, чемreturn v. std::moveотconst— тихая копия.
Типичные вопросы:
- Что делает
std::move? Это перемещение или приведение? - В каком состоянии объект после
std::move? - Зачем
noexceptна move-конструкторе? - Чем отличается
std::moveотstd::forward? - Что такое forwarding reference и чем отличается от обычной
T&&? - Объясните RVO/NRVO и guaranteed copy elision.
- Почему
std::moveотconstне перемещает?