Что такое правило нуля (Rule of Zero) в C++?

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

Ответ

Правило нуля — это современный принцип проектирования классов в C++, который гласит: не определяйте собственные деструктор, конструкторы копирования/перемещения и операторы присваивания копированием/перемещением, если в этом нет абсолютной необходимости. Вместо этого делегируйте управление ресурсами классам-членам, которые сами корректно их обрабатывают (например, стандартным контейнерам и умным указателям). Компилятор автоматически сгенерирует все специальные функции-члены, которые будут вести себя корректно и эффективно.

Сравнение с Правилом трёх/пяти:

  • Правило трёх (до C++11): Если классу нужен пользовательский деструктор, конструктор копирования или оператор присваивания копированием, то, вероятно, нужны все три.
  • Правило пяти (C++11 и выше): Добавляет конструктор перемещения и оператор присваивания перемещением.
  • Правило нуля: Предпочтительнее. Избегайте написания этих функций вручную, используя RAII.

Пример соблюдения Правила нуля:

#include <memory>
#include <vector>
#include <string>

class ResourceManager {
private:
    // Члены-классы сами управляют своими ресурсами.
    std::vector<int> data_;           // Управляет своей памятью.
    std::unique_ptr<NetworkHandle> connection_; // Владение уникальным ресурсом.
    std::shared_ptr<Cache> sharedCache_; // Разделяемое владение.
    std::string name_;                // Управляет строкой.

public:
    ResourceManager(std::vector<int> d, std::string n)
        : data_(std::move(d)), name_(std::move(n)) {}

    // НЕТ пользовательского деструктора, конструкторов копирования/перемещения,
    // операторов присваивания. Компилятор сгенерирует их автоматически, и они
    // будут корректно вызывать соответствующие функции для каждого члена.

    void processData() {
        // Работа с данными...
    }
};

int main() {
    ResourceManager rm1({1, 2, 3}, "First");
    auto rm2 = rm1; // Используется сгенерированный компилятором конструктор копирования.
                    // Он скопирует вектор и строку, но unique_ptr скопировать нельзя,
                    // поэтому эта строка не скомпилируется — что и правильно!

    auto rm3 = std::move(rm1); // Используется сгенерированный конструктор перемещения.
                               // Ресурсы эффективно перемещены из rm1 в rm3.
    return 0;
}

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

  • Упрощение кода: Меньше boilerplate-кода для написания и поддержки.
  • Повышение надёжности: Исключаются ошибки, связанные с ручным управлением памятью и ресурсами (утечки, двойное удаление).
  • Оптимальная производительность: Компилятор может генерировать более эффективные тривиальные (trivial) функции или функции noexcept для перемещения.
  • Безопасность при исключениях: Классы-члены со строгой гарантией исключений обеспечивают её и для вашего класса.