Введение в семантику перемещения
Перестаньте копировать то, что можно забрать — как современный C++ перемещает ресурсы вместо их дублирования.
std::vector), копирование дублирует этот ресурс байт за байтом. Перемещение же передаёт владение существующим ресурсом новому объекту, оставляя старый пустым. Тот же результат — за долю усилий. Семантика перемещения — это возможность языка (появилась в C++11 и совершенствуется с тех пор), которая делает это безопасным и автоматическим.
- отличать lvalue от rvalue и объяснять, почему это различие важно,
- описывать, чего стоит копирование и как перемещение его избегает,
- правильно использовать
std::move— и объяснять, что сам по себе он ничего не перемещает, - рассуждать о состоянии объекта после того, как из него переместили,
- распознавать, где компилятор перемещает за вас автоматически (возвраты, вставка в контейнеры).
1. lvalue против rvalue: есть ли у него имя, на которое можно указать?
Каждое выражение в C++ является либо lvalue, либо rvalue (есть и более тонкие категории, но эти две передают суть). Названия происходят от сторон присваивания: исторически lvalue могло стоять слева от =, а rvalue — только справа.
- lvalue — это объект со стабильной идентичностью: именованное место в памяти, у которого можно взять адрес. Переменные — это lvalue:
x,account.balance,numbers[3]. - rvalue — это временное значение без постоянного имени, результат выражения, которое вот-вот исчезнет. Литералы и результаты большинства выражений — это rvalue:
42,a + b,std::string("hi"), значение, возвращаемое функцией.
Практический тест: если у него можно взять адрес через & — это lvalue; если нельзя — это rvalue.
int x = 10;
int& a = x; // OK: x - lvalue, у него есть адрес
// int& b = 10; // ОШИБКА: 10 - rvalue, адреса для привязки нет
int&& c = 10; // OK: rvalue-ССЫЛКА может привязаться к временному 10
int&& d = x + 5; // OK: (x + 5) - временное rvalue
Последний фрагмент синтаксиса — int&&, двойной амперсанд — это rvalue-ссылка. Это ссылка, которая привязывается только к rvalue (временным). Именно на этом держится семантика перемещения: функция может предложить одну перегрузку для const T& (привязывается к чему угодно, обещает не менять) и другую для T&& (привязывается только к временным, которые вызывающему больше не нужны). Когда выбрана вторая, функция знает, что забрать содержимое аргумента безопасно.