Правило трёх (C++)

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

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

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

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

Правило пяти

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

  • Деструктор
  • Конструктор копирования
  • Оператор присваивания копированием
  • Конструктор перемещения
  • Оператор присваивания перемещением[4]

Пример правила пяти:

#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;//  }};

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

Всегда стоит избегать дублирования одного и того же кода, так как при изменении или исправлении одного участка придётся не забыть исправить остальные. Идиома копирования и обмена (англ. copy and swap idiom) позволяет этого избежать, используя повторно код конструктора копирования, так для класса 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();.

Правило ноля

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

Ссылки

🔥 Top keywords: Заглавная страницаЯндексДуров, Павел ВалерьевичСлужебная:ПоискYouTubeЛунин, Андрей АлексеевичПодносова, Ирина ЛеонидовнаВКонтактеФоллаут (телесериал)WildberriesTelegramРеал Мадрид (футбольный клуб)Богуславская, Зоя БорисовнаДуров, Валерий СемёновичРоссияXVideosСписок умерших в 2024 годуЧикатило, Андрей РомановичFallout (серия игр)Список игроков НХЛ, забросивших 500 и более шайбПопков, Михаил ВикторовичOzon17 апреляИльин, Иван АлександровичMail.ruСёгун (мини-сериал, 2024)Слово пацана. Кровь на асфальтеПутин, Владимир ВладимировичЛига чемпионов УЕФАГагарина, Елена ЮрьевнаБишимбаев, Куандык ВалихановичЛига чемпионов УЕФА 2023/2024Турнир претендентов по шахматам 2024Манчестер СитиMGM-140 ATACMSРоссийский миротворческий контингент в Нагорном КарабахеЗагоризонтный радиолокаторПинапВодительское удостоверение в Российской Федерации