Что такое правило трёх в C++?

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

Ответ

Правило трёх (Rule of Three) — это принцип C++, который гласит: если классу требуется явно определённый пользовательский деструктор, конструктор копирования или копирующий оператор присваивания, то, вероятно, ему требуются все три. Это следствие необходимости корректного управления ресурсами (память, файловые дескрипторы, сокеты и т.д.).

Почему это важно? Без явного определения этих методов компилятор сгенерирует их автоматически (поверхностное копирование). Для классов, владеющих сырыми указателями, это приводит к двойному освобождению памяти или утечкам.

Пример нарушения и исправления:

// НАРУШЕНИЕ: Класс с сырым указателем и только деструктором.
class BadString {
    char* data;
public:
    BadString(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    ~BadString() { delete[] data; } // Только деструктор!
    // Проблема: компилятор сгенерирует конструктор копирования и оператор присваивания,
    // которые просто скопируют указатель. Два объекта будут указывать на одну область памяти.
};

// СОБЛЮДЕНИЕ ПРАВИЛА ТРЁХ:
class String {
    char* data;
public:
    String(const char* str = "") {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    ~String() { delete[] data; } // 1. Деструктор

    // 2. Конструктор копирования (глубокое копирование)
    String(const String& other) {
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);
    }

    // 3. Копирующий оператор присваивания (с проверкой на самоприсваивание)
    String& operator=(const String& other) {
        if (this != &other) { // Защита от самоприсваивания (a = a)
            delete[] data; // Освобождаем старые ресурсы
            data = new char[strlen(other.data) + 1];
            strcpy(data, other.data);
        }
        return *this;
    }
};

Современный C++ (C++11 и новее): Правило пяти (Rule of Five) расширяет правило тремя, добавляя конструктор перемещения и перемещающий оператор присваивания для эффективной работы с rvalue-ссылками. Лучшей практикой является использование Правила нуля (Rule of Zero): проектировать классы так, чтобы управление ресурсами делегировалось умным указателям (std::unique_ptr, std::shared_ptr) и стандартным контейнерам, тогда все пять специальных функций будут сгенерированы корректно автоматически.