Перейти к содержанию

Семантика перемещения в деталях

Начало урока

Семантика перемещения в деталях

Красть ресурсы вместо копирования - и делать это безопасно

Главная идея: копирование дублирует ресурсы, которыми владеет объект; перемещение же передаёт владение этими ресурсами от источника к приёмнику, оставляя источник в корректном, но пустом состоянии. Когда источник - временный объект или явно приведён через std::move, компилятор может выбрать дешёвый путь перемещения вместо дорогого копирования. При грамотном применении семантика перемещения превращает глубокие копии за O(n) в обмен указателями за O(1) - и вызывающий код при этом не меняется.
К концу темы вы сможете:
  • читать категории значений и точно знать, когда связывается rvalue-ссылка T&&;
  • писать корректные конструктор перемещения и оператор перемещающего присваивания;
  • объяснять, почему пометка noexcept меняет производительность программы, а не только её обещания;
  • применять правило пяти (и предпочитать правило нуля);
  • рассуждать о состоянии после перемещения, не вызывая неопределённого поведения.

1. Категории значений и rvalue-ссылки

У каждого выражения в C++ есть и тип, и категория значения. Для практической семантики перемещения важно разделение на lvalue - сущности с именем и устойчивым адресом, который можно взять через &, - и rvalue - временные и другие объекты, чьи ресурсы безопасно забирать, потому что их больше никто не увидит.

rvalue-ссылка, записываемая как T&&, связывается только с rvalue. В этом весь фокус: перегрузка с параметром T&& выбирается именно тогда, когда аргумент - истекающее значение, поэтому внутри такой перегрузки красть его ресурсы безопасно.

void f(const std::string&  s);  // перегрузка для lvalue: берёт что угодно, красть нельзя
void f(std::string&&        s);  // перегрузка для rvalue: берёт временные, красть можно

std::string name = "Ada";
f(name);              // вызовет f(const std::string&)  - name это lvalue
f("Ada");             // вызовет f(std::string&&)        - временное это rvalue
f(std::move(name));   // вызовет f(std::string&&)        - ОБЕЩАЕМ больше не трогать name

std::move ничего не перемещает. Это приведение типа: он выдаёт rvalue-ссылку на свой аргумент, чтобы была выбрана перемещающая перегрузка. Сама работа происходит внутри выбранного конструктора или оператора перемещения. Считайте std::move фразой "относись к этому как к истекающему" - перемещает сам тип, а не приведение.

Именованная rvalue-ссылка сама по себе является lvalue. Внутри void g(std::string&& s) параметр s имеет имя, поэтому, чтобы переместить из него, снова нужно написать std::move(s). Забыв это, вы молча получите копию.

Продолжите этот урок на YourAIZone

В полном уроке — объяснение целиком, разобранные примеры, частые ошибки, тесты с мгновенной проверкой и ИИ-репетитор. Начните сразу как гость или создайте аккаунт, чтобы сохранять прогресс.

В полном уроке

  • Объяснение целиком
  • Разобранные примеры
  • Частые ошибки и как их избежать
  • Тестовые вопросы: 15
  • ИИ-репетитор, который отвечает на вопросы по теме
  • Отслеживание прогресса для ученика, родителей и учителя