← Назад к вопросам

Что такое правило пяти?

2.0 Middle🔥 161 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Правило пяти (Rule of Five)

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

Пять методов

  1. Деструктор (~Class)
  2. Конструктор копирования (Class(const Class&))
  3. Оператор присваивания копирования (Class& operator=(const Class&))
  4. Конструктор перемещения (Class(Class&&)) — C++11
  5. Оператор присваивания перемещения (Class& operator=(Class&&)) — C++11

Почему это правило нужно

Проблема: управление ресурсами

Когда класс управляет динамической памятью, файлами или другими ресурсами, копирование и перемещение объектов требует специального обращения:

// ПЛОХО - нарушение правила пяти
class Buffer {
private:
    char* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new char[size];
    }
    ~Buffer() {
        delete[] data;  // Деструктор определён
        // Но copy/move НЕ определены!
    }
};

Buffer b1(100);
Buffer b2 = b1;  // Shallow copy!
                 // b1.data и b2.data указывают на одно место
// При уничтожении b2 - delete[] data
// При уничтожении b1 - double delete! КРАХ!

Правильная реализация

class Buffer {
private:
    char* data;
    size_t size;
public:
    // Конструктор
    explicit Buffer(size_t s) : size(s) {
        data = new char[size];
    }
    
    // Деструктор
    ~Buffer() {
        delete[] data;
    }
    
    // Копирование
    Buffer(const Buffer& other) : size(other.size) {
        data = new char[size];
        std::memcpy(data, other.data, size);
    }
    
    Buffer& operator=(const Buffer& other) {
        if (this == &other) return *this;
        
        delete[] data;
        size = other.size;
        data = new char[size];
        std::memcpy(data, other.data, size);
        return *this;
    }
    
    // Перемещение (C++11)
    Buffer(Buffer&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    
    Buffer& operator=(Buffer&& other) noexcept {
        if (this == &other) return *this;
        
        delete[] data;
        data = other.data;
        size = other.size;
        other.data = nullptr;
        other.size = 0;
        return *this;
    }
};

Смартные указатели - правило нуля

Современный подход: используй std::unique_ptr и std::shared_ptr — они сами реализуют правило пяти:

class Buffer {
private:
    std::unique_ptr<char[]> data;
    size_t size;
public:
    explicit Buffer(size_t s) : size(s) {
        data = std::make_unique<char[]>(size);
    }
    // Остальные пять методов автоматически generated!
    // Деструктор, копирование (=delete), перемещение - всё работает
};

Это называется правилом нуля — не определяй ничего, пусть компилятор сделает это.

Практический чек-лист

Спросите себя:

  • Управляю ли я динамической памятью? → Используй unique_ptr
  • Управляю ли я файлами? → Используй RAII (Resource Acquisition Is Initialization)
  • Нужно ли глубокое копирование? → Определи все пять методов
  • Хочу запретить копирование? → Используй = delete
class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(NonCopyable&&) = default;
};

Различие Copy vs Move

Copy: глубокое копирование данных

Buffer copy = original;  // Новые данные в памяти
// original и copy - независимы

Move: передача владения ресурсами

Buffer moved = std::move(original);  // Передача владения
// original больше неиспользуем
// Эффективнее для больших объектов

Полезные модификаторы

// noexcept - перемещение не должно выбросить исключение
Buffer(Buffer&& other) noexcept { ... }

// = default - используй сгенерированную версию
~Buffer() = default;

// = delete - явно запрети
Buffer(const Buffer&) = delete;

// const для копирования (читаем source)
Buffer(const Buffer& other) { ... }

// && для перемещения (владеем исходником)
Buffer(Buffer&& other) { ... }

Реальные примеры

String класс:

  • Требует копирования и перемещения
  • Реализует все пять методов
  • Move значительно быстрее

Vector класс:

  • При переразмещении использует move для элементов
  • Требует noexcept move для strong exception guarantee

Слушатели (observers):

  • Часто NonCopyable (запрещены копии)
  • Перемещение разрешено

Правило пяти — это не формальность, а критическая требование для надёжного C++ кода без утечек памяти.

Что такое правило пяти? | PrepBro