Назад к вопросам
Middle+
184
questionbank

Знаешь ли ты правило пяти и правило нуля в C++?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Правило пяти (rule of five) требует явного определения пяти специальных функций-членов, если требуется контролировать копирование, перемещение или разрушение объекта:

  1. Деструктор (~Class()): Освобождает ресурсы.
  2. Конструктор копирования (Class(const Class& other)): Создает новый объект как копию существующего.
  3. Оператор присваивания копированием (Class& operator=(const Class& other)): Присваивает содержимое одного объекта другому.
  4. Конструктор перемещения (Class(Class&& other)): Создает новый объект, "крадя" ресурсы у существующего временного объекта.
  5. Оператор присваивания перемещением (Class& operator=(Class&& other)): Перемещает ресурсы из существующего временного объекта в текущий.

Если хотя бы одна из этих функций определена явно, компилятор перестает автоматически генерировать остальные (или генерирует их в зависимости от правил C++11/14/17). Это правило пришло на смену правилу трех (деструктор, конструктор копирования, оператор присваивания копированием) с появлением семантики перемещения в C++11.

Правило нуля (rule of zero) гласит: если класс не управляет каким-либо ресурсом, то все пять (или три) специальные функции не должны быть определены явно. Вместо этого, следует полагаться на сгенерированные компилятором версии или использовать RAII-объекты (Resource Acquisition Is Initialization), такие как std::vector, std::string, std::unique_ptr, которые сами управляют ресурсами. Класс, использующий такие объекты, автоматически получает корректные сгенерированные специальные функции. Это предпочтительный подход в современном C++, так как снижает вероятность ошибок при управлении ресурсами и упрощает код.

Пример, иллюстрирующий правило пяти (неправильное управление ресурсом):

// Простой пример с ручным управлением памятью
class BadResourceHolder {
public:
    int* data;
    size_t size;

    // Конструктор
    BadResourceHolder(size_t s) : size(s) {
        data = new int[size];
        // Инициализация, если нужно
    }

    // Деструктор (освобождение ресурса)
    ~BadResourceHolder() {
        delete[] data; // Освобождаем память
    }

    // Конструктор копирования (глубокая копия)
    BadResourceHolder(const BadResourceHolder& other) : size(other.size) {
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = other.data[i];
        }
    }

    // Оператор присваивания копированием (глубокая копия)
    BadResourceHolder& operator=(const BadResourceHolder& other) {
        if (this != &other) { // Проверка на самоприсваивание
            delete[] data; // Освобождаем старый ресурс
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i) {
                data[i] = other.data[i];
            }
        }
        return *this;
    }

    // Конструктор перемещения
    BadResourceHolder(BadResourceHolder&& other) noexcept // noexcept важен
        : data(other.data), size(other.size) {
        other.data = nullptr; // Обнуляем оригинальный указатель
        other.size = 0;
    }

    // Оператор присваивания перемещением
    BadResourceHolder& operator=(BadResourceHolder&& other) noexcept {
        if (this != &other) { // Проверка на самоприсваивание
            delete[] data; // Освобождаем старый ресурс

            data = other.data; // "Крадем" ресурс
            size = other.size;

            other.data = nullptr; // Обнуляем оригинальный указатель
            other.size = 0;
        }
        return *this;
    }
};

Пример, иллюстрирующий правило нуля (использование RAII):

// Правильный пример с использованием RAII (std::vector)
#include <vector>

class GoodResourceHolder {
public:
    std::vector<int> data; // std::vector управляет памятью

    // Конструктор
    GoodResourceHolder(size_t s) : data(s) {
        // std::vector сам выделяет память specified by s
    }

    // Нет необходимости определять:
    // - Деструктор
    // - Конструктор копирования
    // - Оператор присваивания копированием
    // - Конструктор перемещения
    // - Оператор присваивания перемещением
    // Компилятор сгенерирует их корректно, благодаря std::vector.
};