Правило трёх (также известное как «Закон Большой Тройки» или «Большая Тройка») — правило в C++, гласящее, что если класс или структура определяет один из следующих методов, то они должны явным образом определить все три метода:

Деструктор Конструктор копирования Оператор присваивания копированием

Эти три метода являются особыми членами-функциями, автоматически создаваемыми компилятором в случае отсутствия их явного объявления программистом. Если один из них должен быть определён программистом, то это означает, что версия, сгенерированная компилятором, не удовлетворяет потребностям класса в одном случае и, вероятно, не удовлетворит в остальных случаях.

Поправка к этому правилу заключается в том, что если используется RAII (от ), то используемый деструктор может остаться неопределённым (иногда упоминается как «Закон Большой Двойки»).

Так как неявно определённые конструкторы и операторы присваивания просто копируют все члены-данные класса, определение явных конструкторов копирования и операторов присваивания копированием необходимо в случаях, когда класс инкапсулирует сложные структуры данных или может поддерживать эксклюзивный доступ к ресурсам. А также в случаях, когда класс содержит константные данные или ссылки.

Правило пяти С выходом одиннадцатого стандарта правило расширилось и стало называться правилом пяти. Теперь при реализации конструктора необходимо реализовать: Деструктор Конструктор копирования Оператор присваивания копированием Конструктор перемещения Оператор присваивания перемещением

Пример правила пяти: #include <cstring>

class RFive { private: char* cstring;

public: // Конструктор со списком инициализации и телом RFive(const char* arg) : cstring(new char[std::strlen(arg)+1])

std::strcpy(cstring, arg);

// Деструктор ~RFive()

delete[] cstring;

// Конструктор копирования RFive(const RFive& other)

cstring = new char[std::strlen(other.cstring) + 1]; std::strcpy(cstring, other.cstring);

// Конструктор перемещения, noexcept - для оптимизации при использовании стандартных контейнеров RFive(RFive&& other) noexcept

cstring = other.cstring; other.cstring = nullptr;

// Оператор присваивания копированием (copy assignment) RFive& operator=(const RFive& other)

if (this == &other) return *this;

char* tmp_cstring = new char[std::strlen(other.cstring) + 1]; std::strcpy(tmp_cstring, other.cstring); delete[] cstring; cstring = tmp_cstring; return *this;

// Оператор присваивания перемещением (move assignment) RFive& operator=(RFive&& other) noexcept

if (this == &other) return *this;

delete[] cstring; cstring = other.cstring; other.cstring = nullptr; return *this;

// Также можно заменить оба оператора присваивания следующим оператором // RFive& operator=(RFive other) // { // std::swap(cstring, other.cstring); // return *this; // }

Идиома копирования и обмена

Всегда стоит избегать дублирования одного и того же кода, так как при изменении или исправлении одного участка придётся не забыть исправить остальные. Идиома копирования и обмена () позволяет этого избежать, используя повторно код конструктора копирования, так для класса RFive придётся создать дружественную функцию swap и реализовать оператор присваивания копированием и перемещением через неё. Более того, при такой реализации отпадает нужда в проверке на самоприсваивание. #include <cstring> class RFive { // остальной код RFive& operator=(const RFive& other) // Оператор присваивания копированием (copy assignment)

Rfive tmp(other); swap (*this, tmp); return *this;

RFive& operator=(RFive&& other) // Оператор присваивания перемещением (move assignment)

swap (*this, other); return *this;

friend void swap (RFive& l, RFive& r)

using std::swap; swap (l.cstring , r.cstring);

// остальной код

Также уместно будет для операторов присваивания сделать возвращаемое значение константной ссылкой: const RFive& operator=(const RFive& other);. Дополнительный const не позволит нам писать запутанный код, как, например, такой: (a=b=c).foo();.

Правило ноля Мартин Фернандес предложил также правило ноля. По этому правилу не стоит определять ни одну из пяти функций самому; надо поручить их создание компилятору (присвоить им значение = default;). Для владения ресурсами вместо простых указателей стоит использовать специальные классы-обёртки, такие как std::unique_ptr и std::shared_ptr.

Примечания

Категория:Синтаксис C++