Делайте операции по умолчанию согласованными
Причина
Операции по умолчанию концептуально являются согласованным набором. Их семантика взаимосвязана. Пользователи будут удивлены, если конструкторы копирования/перемещения и операторы копирующего/перемещающего присваивания выполняют логически разные действия. Пользователи будут удивлены, если конструкторы и деструкторы не обеспечивают согласованного взгляда на управление ресурсами. Пользователи будут удивлены, если копирование и перемещение не отражают работу конструкторов и деструкторов.
Пример (плохой)
class Silly { // ПЛОХО: Несогласованные операции копирования
class Impl {
// ...
};
shared_ptr<Impl> p;
public:
Silly(const Silly& a) : p(make_shared<Impl>()) { *p = *a.p; } // глубокое копирование
Silly& operator=(const Silly& a) { p = a.p; return *this; } // поверхностное копирование
// ...
};
Эти операции расходятся в семантике копирования. Это приведёт к путанице и ошибкам.
Контроль
- (Сложный) Конструктор копирования/перемещения и соответствующий оператор копирующего/перемещающего присваивания должны писать в одни и те же члены данных на одном уровне разыменования.
- (Сложный) Любые члены данных, записываемые в конструкторе копирования/перемещения, должны также инициализироваться во всех остальных конструкторах.
- (Сложный) Если конструктор копирования/перемещения выполняет глубокое копирование члена данных, деструктор должен изменять этот член данных.
- (Сложный) Если деструктор изменяет член данных, этот член данных должен записываться в любом конструкторе копирования/перемещения или операторе присваивания.
C.dtor: Деструкторы
«Нужен ли этому классу деструктор?» — удивительно проницательный вопрос дизайна. Для большинства классов ответ — «нет», либо потому что класс не владеет ресурсами, либо потому что уничтожение обрабатывается правилом нуля; то есть его члены могут сами позаботиться о себе при уничтожении. Если ответ — «да», многое в дизайне класса определяется этим (см. правило пяти).