← Назад к вопросам

Что такое swap-идиома?

2.0 Middle🔥 111 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое swap-идиома?

Краткий ответ

Swap-идиома (copy-and-swap idiom) — это техника в C++ для безопасной реализации оператора присваивания, которая гарантирует сильную гарантию исключений и простоту кода. Вместо того чтобы писать сложную логику присваивания, мы создаём временную копию объекта и обмениваем данные.

Проблема без swap-идиомы

Рассмотрим стандартный способ реализации оператора присваивания:

class MyString {
private:
    char* data;
    size_t length;

public:
    MyString& operator=(const MyString& other) {
        if (this == &other) return *this;  // Self-assignment check
        
        delete[] data;
        data = new char[other.length + 1];
        strcpy(data, other.data);
        length = other.length;
        
        return *this;
    }
};

Проблемы:

  1. Исключение при выделении памяти — если new выбросит исключение после delete, объект будет повреждён
  2. Самоприсваивание — нужна явная проверка if (this == &other)
  3. Сложность — много операций, легко допустить ошибку
  4. Дублирование кода — логика копирования есть и в копирующем конструкторе, и в операторе присваивания

Как работает swap-идиома

Идея: вместо изменения текущего объекта, создаём новый объект (копию) и обмениваемся с ним данными.

class MyString {
private:
    char* data;
    size_t length;

public:
    // Копирующий конструктор (обычный, безопасный)
    MyString(const MyString& other)
        : data(nullptr), length(0) {
        if (other.data) {
            data = new char[other.length + 1];
            strcpy(data, other.data);
            length = other.length;
        }
    }

    // Swap функция (очень эффективная, не выбрасывает исключений!)
    void swap(MyString& other) noexcept {
        std::swap(data, other.data);
        std::swap(length, other.length);
    }

    // Оператор присваивания по копированию используя swap-идиому
    MyString& operator=(const MyString& other) {
        MyString temp(other);  // Создаём копию (может выбросить исключение)
        swap(temp);              // Обмениваемся данными (noexcept!)
        return *this;            // temp уничтожается, освобождая старые данные
    }

    ~MyString() {
        delete[] data;
    }
};

Почему это безопасно:

  1. Если конструктор копирования выбросит исключение, исходный объект не изменится
  2. Функция swap очень простая и не выбрасывает исключений (noexcept)
  3. Нет проверки на самоприсваивание — она происходит автоматически

Пример с вектором

Рассмотрим более сложный пример:

class Container {
private:
    std::vector<int> data;
    std::string name;

public:
    Container() = default;
    
    // Копирующий конструктор (по умолчанию работает правильно)
    Container(const Container&) = default;
    
    // Деструктор (по умолчанию работает правильно)
    ~Container() = default;
    
    // Swap-функция
    void swap(Container& other) noexcept {
        data.swap(other.data);
        name.swap(other.name);
    }
    
    // Оператор присваивания
    Container& operator=(const Container& other) {
        Container temp(other);
        swap(temp);
        return *this;
    }
};

Вот так просто!

С семантикой перемещения (C++11)

В C++11 swap-идиома становится ещё проще благодаря семантике перемещения:

class MyClass {
private:
    std::vector<int> data;
    std::string name;

public:
    MyClass() = default;
    ~MyClass() = default;
    
    // Копирующий конструктор и присваивание
    MyClass(const MyClass&) = default;
    MyClass& operator=(const MyClass& other) {
        MyClass temp(other);
        swap(temp);
        return *this;
    }
    
    // Конструктор и присваивание перемещением
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&& other) noexcept {
        swap(other);
        return *this;
    }
    
    // Swap-функция
    void swap(MyClass& other) noexcept {
        data.swap(other.data);
        name.swap(other.name);
    }
};

Заметьте: оператор присваивания перемещением просто вызывает swap, потому что перемещение — это уже "обмен" данными!

Практический пример: обмен содержимого

MyString a("Hello");
MyString b("World");

// Старый способ (много копирований)
MyString temp = a;
a = b;
b = temp;

// С swap-идиомой (одна операция)
std::swap(a, b);

ADL и std::swap

В современном C++ рекомендуется определять swap как глобальную функцию в том же namespace:

namespace my_namespace {
    class MyClass { ... };
    
    void swap(MyClass& a, MyClass& b) noexcept {
        a.swap(b);
    }
}

Тогда, благодаря ADL (Argument-Dependent Lookup), правильная версия swap найдётся автоматически:

MyClass a, b;
std::swap(a, b);  // Найдёт my_namespace::swap благодаря ADL

Гарантии исключений

Swap-идиома обеспечивает сильную гарантию исключений (strong exception guarantee):

Container c1, c2;
try {
    c1 = c2;  // Если выбросится исключение, c1 остаётся неизменённым!
} catch (...) {
    // c1 всё ещё в корректном состоянии
}

Это очень важно для надёжности кода.

Когда использовать swap-идиому

  • Всегда, когда реализуете оператор присваивания с копированием
  • Для контейнеров с управлением ресурсами
  • В production коде, где важна безопасность

Современный C++ (C++20 и позже)

В новых стандартах часто используется просто:

class MyClass {
private:
    std::vector<int> data;

public:
    MyClass() = default;
    ~MyClass() = default;
    MyClass(const MyClass&) = default;
    MyClass& operator=(const MyClass&) = default;
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;
    
    friend void swap(MyClass& a, MyClass& b) noexcept {
        std::swap(a.data, b.data);
    }
};

Компилятор сгенерирует всё правильно автоматически!

Заключение

Swap-идиома — это элегантный способ реализовать безопасное и простое присваивание, который:

  • Гарантирует сильную гарантию исключений
  • Избегает самоприсваивания
  • Упрощает код
  • Работает со всеми типами, имеющими корректный swap

Это одна из самых важных идиом в C++ при работе с управлением ресурсами.

Что такое swap-идиома? | PrepBro