Правило трёх/пяти/нуля
Правило трёх
Если классу требуется пользовательский деструктор, пользовательский конструктор копирования или пользовательский оператор присваивания копированием, почти наверняка ему требуются все три.
Поскольку C++ копирует объекты пользовательских типов и присваивает им значения в различных ситуациях (передача/возврат по значению, работа с контейнером и т. д.), эти специальные функции-члены будут вызваны, если они доступны. Если же они не определены пользователем, компилятор определит их неявно.
Не следует использовать неявно определённые специальные функции-члены, если класс управляет ресурсом, дескриптор которого имеет тип, не являющийся типом класса (сырой указатель, дескриптор файла POSIX и т. д.), деструктор ничего не делает, а конструктор копирования/оператор присваивания выполняет «поверхностное копирование» (копирует значение дескриптора, не дублируя лежащий в его основе ресурс).
#include <cstddef>
#include <cstring>
#include <iostream>
#include <utility>
class rule_of_three
{
char* cstring; // raw pointer used as a handle to a
// dynamically-allocated memory block
public:
explicit rule_of_three(const char* s = "") : cstring(nullptr)
{
if (s)
{
cstring = new char[std::strlen(s) + 1]; // allocate
std::strcpy(cstring, s); // populate
}
}
~rule_of_three() // I. destructor
{
delete[] cstring; // deallocate
}
rule_of_three(const rule_of_three& other) // II. copy constructor
: rule_of_three(other.cstring) {}
rule_of_three& operator=(const rule_of_three& other) // III. copy assignment
{
// implemented through copy-and-swap for brevity
// note that this prevents potential storage reuse
rule_of_three temp(other);
std::swap(cstring, temp.cstring);
return *this;
}
const char* c_str() const // accessor
{
return cstring;
}
};
int main()
{
rule_of_three o1{"abc"};
std::cout << o1.c_str() << ' ';
auto o2{o1}; // II. uses copy constructor
std::cout << o2.c_str() << ' ';
rule_of_three o3("def");
std::cout << o3.c_str() << ' ';
o3 = o2; // III. uses copy assignment
std::cout << o3.c_str() << '\n';
} // I. all destructors are called here
Вывод:
abc abc def abc
Классам, управляющим некопируемыми ресурсами посредством копируемых дескрипторов, может потребоваться объявить оператор присваивания копированием и конструктор копирования private и не предоставлять их определения(до C++11)определить оператор присваивания копированием и конструктор копирования как = delete(начиная с C++11). Это ещё одно применение правила трёх: удалить одну функцию, оставив другую неявно определяемой, как правило, неправильно.
Правило пяти
Поскольку наличие пользовательского (включая = default или объявленного как = delete) деструктора, конструктора копирования или оператора присваивания копированием препятствует неявному определению конструктора перемещения и оператора присваивания перемещением, любой класс, для которого желательна семантика перемещения, должен объявить все пять специальных функций-членов:
class rule_of_five
{
char* cstring; // raw pointer used as a handle to a
// dynamically-allocated memory block
public:
explicit rule_of_five(const char* s = "") : cstring(nullptr)
{
if (s)
{
cstring = new char[std::strlen(s) + 1]; // allocate
std::strcpy(cstring, s); // populate
}
}
~rule_of_five()
{
delete[] cstring; // deallocate
}
rule_of_five(const rule_of_five& other) // copy constructor
: rule_of_five(other.cstring) {}
rule_of_five(rule_of_five&& other) noexcept // move constructor
: cstring(std::exchange(other.cstring, nullptr)) {}
rule_of_five& operator=(const rule_of_five& other) // copy assignment
{
// implemented as move-assignment from a temporary copy for brevity
// note that this prevents potential storage reuse
return *this = rule_of_five(other);
}
rule_of_five& operator=(rule_of_five&& other) noexcept // move assignment
{
std::swap(cstring, other.cstring);
return *this;
}
// alternatively, replace both assignment operators with copy-and-swap
// implementation, which also fails to reuse storage in copy-assignment.
// rule_of_five& operator=(rule_of_five other) noexcept
// {
// std::swap(cstring, other.cstring);
// return *this;
// }
};
В отличие от правила трёх, отсутствие конструктора перемещения и оператора присваивания перемещением обычно не является ошибкой, но означает упущенную возможность оптимизации.
Правило нуля
Классы с пользовательскими деструкторами, конструкторами копирования/перемещения или операторами присваивания копированием/перемещением должны заниматься исключительно управлением владением (что следует из принципа единственной ответственности). У остальных классов не должно быть пользовательских деструкторов, конструкторов копирования/перемещения или операторов присваивания копированием/перемещением[1].
Это правило также представлено в основных рекомендациях C++ как C.20: Если можно не определять операции по умолчанию, не определяйте их.
class rule_of_zero
{
std::string cppstring;
public:
rule_of_zero(const std::string& arg) : cppstring(arg) {}
};
Если базовый класс предназначен для полиморфного использования, его деструктор может потребоваться объявить public и virtual. Это блокирует неявное перемещение (и делает неявное копирование устаревшим), поэтому специальные функции-члены необходимо определить как = default[2].
class base_of_five_defaults
{
public:
base_of_five_defaults(const base_of_five_defaults&) = default;
base_of_five_defaults(base_of_five_defaults&&) = default;
base_of_five_defaults& operator=(const base_of_five_defaults&) = default;
base_of_five_defaults& operator=(base_of_five_defaults&&) = default;
virtual ~base_of_five_defaults() = default;
};
Однако это делает класс подверженным срезке, поэтому в полиморфных классах копирование часто определяют как = delete (см. C.67: Полиморфный класс должен запрещать открытое копирование/перемещение в основных рекомендациях C++). Это приводит к следующей общей формулировке правила пяти: C.21: Если вы определяете или =delete любую функцию копирования, перемещения или деструктор, определите или =delete их все.
Внешние ссылки
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/rule_of_three