Знаешь ли ты правило пяти и правило нуля в C++?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
Правило пяти (rule of five) требует явного определения пяти специальных функций-членов, если требуется контролировать копирование, перемещение или разрушение объекта:
- Деструктор (
~Class()): Освобождает ресурсы. - Конструктор копирования (
Class(const Class& other)): Создает новый объект как копию существующего. - Оператор присваивания копированием (
Class& operator=(const Class& other)): Присваивает содержимое одного объекта другому. - Конструктор перемещения (
Class(Class&& other)): Создает новый объект, "крадя" ресурсы у существующего временного объекта. - Оператор присваивания перемещением (
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.
};