Когда вызывается оператор присваивания копированием (copy assignment operator) в C++?

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

Ответ

Оператор присваивания копированием (operator=) вызывается, когда существующему объекту присваивается значение другого существующего объекта того же типа. Это не конструктор, а оператор, который модифицирует состояние уже созданного объекта.

Типичные сценарии вызова:

MyClass a, b, c;

a = b;               // (1) Прямое присваивание одного объекта другому
c = a = b;           // (2) Цепочка присваиваний (возвращает ссылку на левый операнд)

std::vector<MyClass> vec(5);
vec[0] = vec[1];     // (3) Присваивание элементам контейнеров

Ключевые требования к реализации:

  1. Проверка на самоприсваивание: if (this == &other) return *this;
  2. Освобождение старых ресурсов: Перед копированием новых данных нужно корректно освободить ресурсы, которыми владел текущий объект.
  3. Копирование данных: Скопировать (или клонировать) данные из other в текущий объект.
  4. Возврат ссылки на текущий объект: Для поддержки цепочек присваивания.

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

class Buffer {
    char* data;
    size_t size;
public:
    // Конструктор по умолчанию
    Buffer(size_t sz = 0) : size(sz), data(size ? new char[size] : nullptr) {}

    // Деструктор
    ~Buffer() { delete[] data; }

    // Конструктор копирования (для Rule of Three/Five)
    Buffer(const Buffer& other) : size(other.size), data(size ? new char[size] : nullptr) {
        std::copy(other.data, other.data + size, data);
    }

    // Оператор присваивания копированием (Copy Assignment Operator)
    Buffer& operator=(const Buffer& other) {
        if (this != &other) { // (1) КРИТИЧНО: проверка на самоприсваивание
            // (2) Освобождаем старые ресурсы
            delete[] data;
            // (3) Копируем новые данные
            size = other.size;
            data = size ? new char[size] : nullptr;
            std::copy(other.data, other.data + size, data);
        }
        // (4) Возвращаем *this
        return *this;
    }
};

Современный подход (Copy-and-Swap idiom): Часто оператор присваивания реализуют через передачу параметра по значению и обмен (swap), что автоматически обеспечивает строгую гарантию исключений и защиту от самоприсваивания.

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