Что такое идиома copy-and-swap в C++?

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

Ответ

Идиома copy-and-swap — это надежный и эффективный паттерн для реализации оператора присваивания копированием (operator=), обеспечивающий строгую гарантию безопасности исключений и автоматическую поддержку move-семантики.

Суть идиомы: Вместо того чтобы копировать данные внутри operator=, мы создаем временную копию аргумента (используя конструктор копирования или перемещения), а затем обмениваем (swap) её внутреннее состояние с состоянием текущего объекта.

Полная реализация для класса, управляющего ресурсом:

#include <algorithm> // для std::swap
#include <utility>   // для std::exchange

class Buffer {
private:
    int* data_ = nullptr;
    size_t size_ = 0;

    // Дружественная функция обмена
    friend void swap(Buffer& a, Buffer& b) noexcept {
        using std::swap;
        swap(a.data_, b.data_);
        swap(a.size_, b.size_);
    }

public:
    // Конструкторы, деструктор...
    ~Buffer() { delete[] data_; }

    // Ключевой метод обмена
    void swap(Buffer& other) noexcept {
        ::swap(*this, other); // Вызов дружественной функции
    }

    // Оператор присваивания по ИДИОМЕ COPY-AND-SWAP
    Buffer& operator=(Buffer other) noexcept { // ПАРАМЕТР ПЕРЕДАЕТСЯ ПО ЗНАЧЕНИЮ!
        other.swap(*this); // Обменяли состояние с временной копией
        return *this;
        // Деструктор 'other' (который теперь содержит старые данные *this)
        // автоматически освобождает старый ресурс.
    }

    // Конструктор копирования (для полноты примера)
    Buffer(const Buffer& other) : size_(other.size_) {
        data_ = new int[size_];
        std::copy(other.data_, other.data_ + size_, data_);
    }
    // Конструктор перемещения генерируется автоматически или может быть =default
};

Преимущества:

  1. Безопасность исключений: Вся работа, которая может бросить исключение (выделение памяти, копирование), происходит при создании параметра other до изменения состояния текущего объекта. Если это не удастся, *this останется неизменным.
  2. Автоматическая поддержка перемещения: Поскольку параметр передается по значению, компилятор сам вызовет конструктор перемещения для r-value аргументов, делая operator= универсальным.
  3. Устранение дублирования кода: Логика копирования содержится только в конструкторе копирования.