Правило трёх (также известное как «Закон Большой Тройки» или «Большая Тройка») — правило в C++, гласящее, что если класс или структура определяет один из следующих методов, то они должны явным образом определить все три метода[1]:
Эти три метода являются особыми членами-функциями, автоматически создаваемыми компилятором в случае отсутствия их явного объявления программистом. Если один из них должен быть определен программистом, то это означает, что версия, сгенерированная компилятором, не удовлетворяет потребностям класса в одном случае и, вероятно, не удовлетворит в остальных случаях.
Поправка к этому правилу заключается в том, что если используется RAII (от англ. Resource Acquisition Is Initialization), то используемый деструктор может остаться неопределённым (иногда упоминается как «Закон Большой Двойки»)[2].
Так как неявно определённые конструкторы и операторы присваивания просто копируют все члены-данные класса[3], определение явных конструкторов копирования и операторов присваивания копированием необходимо в случаях, когда класс инкапсулирует сложные структуры данных или может поддерживать эксклюзивный доступ к ресурсам. А также в случаях, когда класс содержит константные данные или ссылки.
С выходом одиннадцатого стандарта правило расширилось и теперь называется правило пяти. Теперь при реализации конструктора необходимо реализовать:
Пример правила пяти:
1 #include <cstring>
2
3 class RFive
4 {
5 private:
6 char* cstring;
7
8 public:
9 // Конструктор со списком инициализации и телом
10 RFive(const char* arg)
11 : cstring(new char[std::strlen(arg)+1])
12 {
13 std::strcpy(cstring, arg);
14 }
15
16 // Деструктор
17 ~RFive()
18 {
19 delete[] cstring;
20 }
21
22 // Конструктор копирования
23 RFive(const RFive& other)
24 {
25 cstring = new char[std::strlen(other.cstring) + 1];
26 std::strcpy(cstring, other.cstring);
27 }
28
29 // Конструктор перемещения, noexcept - для оптимизации при использовании стандартных контейнеров
30 RFive(RFive&& other) noexcept
31 {
32 cstring = other.cstring;
33 other.cstring = nullptr;
34 }
35
36 // Оператор присваивания копированием (copy assignment)
37 RFive& operator=(const RFive& other)
38 {
39 if (this == &other)
40 return *this;
41
42 char* tmp_cstring = new char[std::strlen(other.cstring) + 1];
43 std::strcpy(tmp_cstring, other.cstring);
44 delete[] cstring;
45 cstring = tmp_cstring;
46 return *this;
47 }
48
49 // Оператор присваивания перемещением (move assignment)
50 RFive& operator=(RFive&& other) noexcept
51 {
52 if (this == &other)
53 return *this;
54
55 delete[] cstring;
56 cstring = other.cstring;
57 other.cstring = nullptr;
58 return *this;
59 }
60
61 // Также можно заменить оба оператора присваивания следующим оператором
62 // RFive& operator=(RFive other)
63 // {
64 // std::swap(cstring, other.cstring);
65 // return *this;
66 // }
67 };
Всегда стоит избегать дублирования одного и того же кода, так как при изменении или исправлении одного участка, придётся не забыть исправить остальные. Идиома копирования и обмена (англ. copy and swap idiom) позволяет этого избежать, используя повторно код конструктора копирования. Так для класса RFive придётся создать дружественную функцию swap и реализовать оператор присваивания копированием и перемещением через неё. Более того, при такой реализации отпадает нужда в проверке на самоприсваивание.
1 #include <cstring>
2 class RFive
3 {
4 // остальной код
5 RFive& operator=(const RFive& other) // Оператор присваивания копированием (copy assignment)
6 {
7 Rfive tmp(other);
8 swap (*this, tmp);
9 return *this;
10 }
11 RFive& operator=(RFive&& other) // Оператор присваивания перемещением (move assignment)
12 {
13 swap (*this, other);
14 return *this;
15 }
16 friend void swap (RFive& l, RFive& r)
17 {
18 using std::swap;
19 swap (l.cstring , r.cstring);
20 }
21 // остальной код
22 };
Также уместно будет для операторов присваивания сделать возвращаемое значение константной ссылкой: const RFive& operator=(const RFive& other);
. Дополнительный const не позволит нам писать запутанный код, как, например, такой: (a=b=c).foo();
.
Мартин Фернандес предложил также правило ноля.[5]
По этому правилу не стоит определять ни одну из пяти функций самому; надо поручить их создание компилятору(присвоить им значение = default;
). Для владения ресурсами вместо простых указателей стоит использовать специальные классы-обёртки, такие как std::unique_ptr
и std::shared_ptr
.[6]
Это заготовка статьи о программировании. Вы можете помочь проекту, дополнив её. |
Данная страница на сайте WikiSort.ru содержит текст со страницы сайта "Википедия".
Если Вы хотите её отредактировать, то можете сделать это на странице редактирования в Википедии.
Если сделанные Вами правки не будут кем-нибудь удалены, то через несколько дней они появятся на сайте WikiSort.ru .