Расскажи про move-семантику в C++

«Расскажи про move-семантику в C++» — вопрос из категории C++ Core, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Move-семантика — это механизм в C++ (начиная с C++11), который позволяет эффективно передавать владение ресурсами (например, динамической памятью) от одного объекта к другому, избегая дорогостоящих глубоких копий. Она основана на использовании rvalue-ссылок (&&).

Ключевые компоненты:

  • Rvalue-ссылки (T&&): Ссылаются на временные объекты (rvalues), что позволяет их "опустошать".
  • Move-конструктор и move-оператор присваивания: Специальные члены класса, которые "крадут" ресурсы у исходного объекта.
  • std::move: Утилита из <utility>, которая приводит lvalue к rvalue, сигнализируя о том, что объект можно переместить.

Пример класса с move-семантикой:

#include <utility>

class Buffer {
    int* data_;
    size_t size_;
public:
    // Move-конструктор
    Buffer(Buffer&& other) noexcept
        : data_(std::exchange(other.data_, nullptr))
        , size_(std::exchange(other.size_, 0)) {}

    // Move-оператор присваивания
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data_; // Освобождаем свои старые ресурсы
            data_ = std::exchange(other.data_, nullptr);
            size_ = std::exchange(other.size_, 0);
        }
        return *this;
    }

    ~Buffer() { delete[] data_; }
    // ... (конструктор копирования, обычный конструктор и т.д.)
};

// Использование
Buffer createBuffer(size_t n); // Фабричная функция

Buffer b1(1024);
Buffer b2 = std::move(b1); // Вызов move-конструктора, b1 теперь пуст
Buffer b3 = createBuffer(512); // Move-конструктор (возвращаемое значение — rvalue)

Важные правила:

  1. Move-операции должны быть помечены noexcept (особенно для совместимости с контейнерами STL, например, при vector::resize).
  2. После перемещения исходный объект должен находиться в валидном, но неопределенном состоянии (его можно безопасно уничтожить или присвоить ему новое значение).
  3. Компилятор может сгенерировать move-операции по умолчанию, если в классе нет пользовательских копирующих операций, перемещающих операций или деструктора.
  4. Move-семантика лежит в основе оптимизаций, таких как copy elision и Return Value Optimization (RVO).