Как реализуется оператор присваивания копированием по умолчанию в C++?

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

Ответ

Компилятор C++ автоматически генерирует оператор присваивания копированием (operator=), если он не объявлен явно и в классе нет полей, запрещающих копирование (например, ссылок). Этот сгенерированный оператор выполняет почленное копирование (member-wise copy) для всех нестатических полей и базовых подобъектов.

Пример сгенерированного поведения:

class Example {
public:
    int value;
    std::string name;
    int* dynamic_data;
};

Example obj1;
obj1.value = 42;
obj1.name = "Test";
obj1.dynamic_data = new int(100);

Example obj2;
obj2 = obj1; // Вызов сгенерированного operator=
             // obj2.value = obj1.value (копирование int)
             // obj2.name = obj1.name (копирование std::string работает корректно)
             // obj2.dynamic_data = obj1.dynamic_data (ПРОБЛЕМА: копируется указатель, а не данные!)

Ключевые проблемы сгенерированного оператора:

  1. Поверхностное копирование указателей (Shallow Copy): Оба объекта начинают владеть одним и тем же ресурсом в динамической памяти.
  2. Утечки памяти: Старые данные obj2.dynamic_data не освобождаются.
  3. Двойное освобождение: Деструкторы обоих объектов попытаются освободить одну и ту же память, что ведет к неопределенному поведению.

Решение — реализация оператора присваивания копированием (часть "Правила трех/пяти"):

class Example {
    int* dynamic_data;
    size_t size;
public:
    // Пользовательский оператор присваивания копированием
    Example& operator=(const Example& other) {
        if (this != &other) { // Защита от самоприсваивания (a = a)
            // 1. Освобождаем старые ресурсы
            delete[] dynamic_data;
            // 2. Выделяем новую память и копируем данные
            size = other.size;
            dynamic_data = new int[size];
            std::copy(other.dynamic_data, other.dynamic_data + size, dynamic_data);
        }
        return *this; // Возвращаем ссылку на текущий объект
    }
    // ... также нужны конструктор копирования и деструктор
};

Идиома "Copy-and-Swap" — более надежная и безопасная альтернатива, которая также обеспечивает строгую гарантию исключений.

Example& operator=(Example other) { // Параметр передается по значению (копируется)
    swap(*this, other); // Обмениваем содержимое текущего объекта и копии
    return *this;
    // Деструктор параметра `other` освободит старые ресурсы
}