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

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

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

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

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

Правило пяти

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

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

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

#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(); .

Правило ноля

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

Ссылки

  1. Bjarne Stroustrup . The C++ Programming Language (неопр.) . — 3. — Addison-Wesley , 2000. — С. 283—284. — ISBN 978-0201700732 .
  2. Karlsson, Bjorn; Wilson, Matthew.: . The C++ Source . Artima (1 октября 2004). Дата обращения: 22 января 2008. 17 марта 2012 года.
  3. The C++ Programming Language (неопр.) . — С. 271.
  4. . En.CPPReference.com . Дата обращения: 22 декабря 2014. 23 декабря 2014 года.
  5. . Flaming Dangerzone . Дата обращения: 29 июля 2015. 29 августа 2015 года.
  6. Куликов Александр. . Дата обращения: 14 февраля 2016. 22 февраля 2016 года.
Источник —

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