Виртуальный деструктор базового класса - правило пяти?
У меня есть база State
класс интерфейса с виртуальным деструктором по умолчанию.
class State {
public:
virtual void event() = 0;
virtual ~State() = default; // relevant part
virtual void onCreate() {}
virtual void onDestroy() {}
virtual void onActivate() {}
virtual void onDeactivate() {}
};
А потом унаследованные от него классы:
class GameState : public State {
public:
void event() override;
// ...
};
class MenuState : public State {
public:
void event() override;
// ...
};
Компилятор генерирует операции перемещения по умолчанию, если ни одна операция копирования или деструктор не определены пользователем. Компилятор генерирует операции копирования по умолчанию, если никакая операция перемещения не определена пользователем.
Правильно ли я, объявив виртуальный деструктор по умолчанию, я фактически удалил операции перемещения по умолчанию?
Будут ли операции перемещения работать для производных классов, если базовый класс неявно удалил свои операции перемещения, а базовый класс представляет собой просто интерфейс без членов данных?
Разве здесь действительно разумно следовать Правилу пяти? Явное удаление или использование по умолчанию всех 5 специальных функций-членов кажется довольно непосильной задачей.
1 ответ
Предположим, что ваш базовый класс имеет конструкторы копирования (и, возможно, перемещения) и операторы присваивания. Чего бы вы ожидали в этом случае?
MenuState menuState;
State state = menuState; // be aware that this State object is not a MenuState any more
Если ваш класс абстрактный (например, еслиonActivate()
был чисто виртуальным), выше не имело бы никакого смысла, поскольку экземпляр не был бы разрешен.
Но даже если нет, любойMenuState
члены будут нарезаны, когда вы поместите их вState
объект. Если экземпляры вашего базового класса сами по себе не имеют особого смысла, лучше удалить конструкторы копирования и перемещения, а также операторы присваивания.
Для производных (конкретных) классов копирование или перемещение может иметь больше смысла. В этом случае может быть полезно иметь в базовом классе конструкторы копирования и операторы присваивания. Таким образом, вы не можете создать экземпляр самого базового класса, но ваши производные классы могут его использовать.
MenuState menuState;
MenuState copy = menuState; // might be useful
Если, с другой стороны, базовый класс имеет смысл сам по себе (и, возможно,default
) Конструкторы копирования и перемещения и операторы присваивания могут быть разумными. Это может произойти в том случае, если производные классы просто добавляют некоторую дополнительную информацию, которая не требуется при преобразовании производных объектов в базовые объекты.
Так что это действительно зависит, и поэтому лучше явно указать, что предполагается: копировать и перемещать, копировать и перемещать или не копировать вообще.
Однако такие иерархии классов часто используются с указателями (например,std::unique_ptr
илиstd::shared_ptr
). Затемvirtual
метод иногда более полезен.
auto state = std::make_unique<MenuState>();
std::unique_ptr<State> copy = state->clone();
Однако внутри этот метод может использовать конструктор копирования, который можноpublic
(будьте осторожны при нарезке, если ваши производные классы неfinal
) илиprotected
(если вы хотите убедиться, что толькоclone()
используется метод).