Наследуются ли виртуальные деструкторы?

Если у меня есть базовый класс с виртуальным деструктором. Имеет ли производный класс объявление виртуального деструктора тоже?

class base {
public:
    virtual ~base () {}
};

class derived : base {
public:
    virtual ~derived () {} // 1)
    ~derived () {}  // 2)
};

Конкретные вопросы:

  • Является ли 1) и 2) тем же? Является ли 2) автоматически виртуальным из-за своей базы или "останавливает" виртуальность?
  • Может ли производный деструктор быть опущен, если ему нечего делать?
  • Какая наилучшая практика для объявления производного деструктора? Объявить его виртуальным, не виртуальным или опустить его, если это возможно?

Ответ 1

  • Да, они одинаковы. Производный класс, не объявляющий что-то виртуальное, не мешает ему быть виртуальным. На самом деле нет способа остановить использование любого метода (включенного деструктора) из виртуального в производном классе, если он был виртуальным в базовом классе. В >= С++ 11 вы можете использовать final, чтобы предотвратить его переопределение в производных классах, но это не мешает ему быть виртуальным.
  • Да, деструктор в производном классе может быть опущен, если ему нечего делать. И не имеет значения, действительно ли его виртуальный.
  • Я бы опустил это, если возможно. И я всегда использую ключевое слово virtual снова для виртуальных функций в производных классах по соображениям ясности. Людям не нужно идти по иерархии наследования, чтобы выяснить, что функция виртуальна. Кроме того, если ваш класс может быть скопирован или перемещен без необходимости объявления вашей собственной копии или перемещения конструкторов, объявление деструктора любого типа (даже если вы определяете его как default) заставит вас объявить копию и переместить конструкторы и операторы присваивания если вы хотите их, поскольку компилятор больше не будет их вводить для вас.

В качестве небольшой точки для пункта 3. В комментариях было указано, что если деструктор не объявлен, компилятор генерирует значение по умолчанию (которое по-прежнему является виртуальным). И это значение по умолчанию - встроенная функция.

Встроенные функции потенциально могут привести к большей части вашей программы изменениям в других частях вашей программы и сделать двоичную совместимость для общих библиотек сложной. Кроме того, повышенная связь может привести к перекомпиляции перед лицом определенных видов изменений. Например, если вы решите, что действительно хотите реализовать свой виртуальный деструктор, тогда каждый фрагмент кода, который его вызвал, нужно будет перекомпилировать. Если вы объявили его в теле класса, а затем определили его пустым в файле .cpp, вы могли бы его изменить без перекомпиляции.

Мой личный выбор по-прежнему заключался бы в том, чтобы опустить его, когда это возможно. По-моему, он загромождает код, и компилятор иногда может делать несколько более эффективные вещи с реализацией по умолчанию по пустой. Но есть ограничения, с которыми вы можете столкнуться, что это плохой выбор.

Ответ 2

Виртуальная функция-член сделает неявно любую перегрузку этой функции виртуальной.

Таким образом, виртуальный в 1) является "необязательным", виртуальный деструктор базового класса, являющийся виртуальным, также делает все дочерние деструкторы виртуальными.

Ответ 3

  • Деструктор автоматически виртуальный, как и для всех методов. Вы не можете остановить метод из виртуального в С++ (если он уже был объявлен виртуальным, то есть нет эквивалента "final" в Java)
  • Да, его можно опустить.
  • Я объявляю виртуальный деструктор, если я намереваюсь, чтобы этот класс был подклассифицирован, независимо от того, подклассифицирует ли он другой класс или нет, я также предпочитаю сохранять виртуальные объявления, даже если это не нужно. Это будет поддерживать подклассы, если вы когда-нибудь решите удалить наследование. Но я полагаю, что это всего лишь вопрос стиля.

Ответ 4

1/Да 2/Да, он будет сгенерирован компилятором 3/Выбор между объявлением его виртуальным или нет должен следовать вашему соглашению для переопределения виртуальных членов - IMHO, есть хорошие аргументы в обоих направлениях, просто выберите один и следуйте за ним.

Я бы опустил это, если возможно, но есть одна вещь, которая может побудить вас объявить ее: если вы используете генерируемый компилятором, он неявно встроен. Есть время, когда вы хотите избежать входящих членов (например, динамических библиотек).