Унаследованы ли виртуальные деструкторы?
Если у меня есть базовый класс с виртуальным деструктором. Имеет ли производный класс для объявления виртуального деструктора тоже?
class base {
public:
virtual ~base () {}
};
class derived : base {
public:
virtual ~derived () {} // 1)
~derived () {} // 2)
};
Конкретные вопросы:
- 1) и 2) одно и то же? 2) автоматически виртуален из-за своей базы или "останавливает" виртуальность?
- Может ли производный деструктор быть опущен, если он не имеет ничего общего?
- Какова наилучшая практика для объявления производного деструктора? Объявить это виртуальным, не виртуальным или опустить его, если это возможно?
4 ответа
- Да, они одинаковы. Производный класс, не объявляющий что-то виртуальное, не мешает ему быть виртуальным. Фактически, нет никакого способа помешать любому методу (включая деструктор) быть виртуальным в производном классе, если он был виртуальным в базовом классе. В>=C++11 вы можете использовать
final
чтобы предотвратить его переопределение в производных классах, но это не мешает ему быть виртуальным. - Да, деструктор в производном классе может быть опущен, если он не имеет ничего общего. И не важно, виртуальный он или нет.
- Я бы пропустил это, если это возможно. И я всегда использую
virtual
снова ключевое слово для виртуальных функций в производных классах по соображениям наглядности. Людям не нужно идти вверх по иерархии наследования, чтобы понять, что функция является виртуальной. Кроме того, если ваш класс является копируемым или перемещаемым без необходимости объявлять собственную копию или перемещать конструкторы, объявляя деструктор любого вида (даже если вы определяете его какdefault
) заставит вас объявлять конструкторы копирования и перемещения и операторы присваивания, если вы хотите их, так как компилятор больше не вставит их для вас.
В качестве небольшого замечания по пункту 3. В комментариях было указано, что, если деструктор не объявлен, компилятор генерирует деструктор по умолчанию (который все еще является виртуальным). И эта по умолчанию является встроенной функцией.
Встроенные функции потенциально могут подвергать большую часть вашей программы изменениям в других частях вашей программы и затрудняют бинарную совместимость для разделяемых библиотек. Кроме того, повышенная связь может привести к большой перекомпиляции перед лицом определенных изменений. Например, если вы решите, что действительно хотите реализовать реализацию для своего виртуального деструктора, то каждый фрагмент кода, который его вызвал, необходимо будет перекомпилировать. Тогда как если бы вы объявили его в теле класса, а затем определили его пустым в .cpp
файл вы бы хорошо изменив его без перекомпиляции.
Мой личный выбор все равно будет опускать его, когда это возможно. По моему мнению, это загромождает код, и компилятор может иногда делать немного более эффективные вещи с реализацией по умолчанию над пустой. Но есть ограничения, из-за которых вы можете быть плохим выбором.
- Деструктор автоматически виртуален, как и все методы. Вы не можете помешать методу стать виртуальным в C++ (если он уже был объявлен виртуальным, то есть в Java нет эквивалента 'final')
- Да, это может быть опущено.
- Я бы объявил виртуальный деструктор, если я собираюсь подклассифицировать этот класс, независимо от того, является ли он подклассом другого класса или нет, я также предпочитаю сохранять объявление методов виртуальным, даже если это не нужно. Это сохранит работу подклассов, если вы когда-нибудь решите удалить наследство. Но я полагаю, это просто вопрос стиля.
Виртуальная функция-член неявно делает любую перегрузку этой функции виртуальной.
Таким образом, виртуальный в 1) является "необязательным", а виртуальный деструктор базового класса делает все дочерние деструкторы виртуальными.
Виртуальные функции неявно переопределяются. Когда метод дочернего класса соответствует сигнатуре метода виртуальной функции из базового класса, он переопределяется. Это легко запутать и, возможно, сломать во время рефакторинга, поэтому естьoverride
а также final
ключевые слова, начиная с C++11, чтобы явно отметить это поведение. Есть соответствующие предупреждения, запрещающие бесшумное поведение, например-Wsuggest-override
в GCC.
Есть связанный вопрос для override
а также final
ключевые слова на SO: ключевое слово 'override' - это просто проверка переопределенного виртуального метода?.
И документация в ссылке cpp https://en.cppreference.com/w/cpp/language/override
Стоит ли использовать override
ключевое слово с деструкторами все еще вызывает споры. Например, см. Обсуждение в этом связанном вопросе SO: переопределение виртуального деструктора по умолчанию. Проблема в том, что семантика виртуального деструктора отличается от обычных функций. Деструкторы связаны, поэтому все деструкторы базовых классов вызываются после дочернего. Однако в случае обычного метода базовые реализации переопределенного метода по умолчанию не вызываются. При необходимости их можно вызвать вручную.
1/ Да 2/ Да, он будет сгенерирован компилятором 3/ Выбор между объявлением его виртуальным или нет, должен соответствовать вашему соглашению для переопределенных виртуальных членов - ИМХО, есть хорошие аргументы в обоих случаях, просто выберите один и следуйте ему.
Я бы пропустил это, если это возможно, но есть одна вещь, которая может побудить вас объявить это: если вы используете сгенерированный компилятором, он неявно встроен. Есть время, когда вы хотите избежать встроенных членов (например, динамических библиотек).