Должен ли я по умолчанию виртуальные деструкторы?

У меня есть абстрактный класс, который объявляется следующим образом:

class my_type {
public:
    virtual ~my_type() = default;
    virtual void do_something() = 0;
};

Считается ли хорошей практикой объявлять деструктор, как это, с ключевым словом default? Есть ли лучший способ?

Кроме того, существует = 0 современный (С++ 11) способ указания реализации по умолчанию, или есть лучший способ?

Ответы

Ответ 1

Да, вы можете определенно использовать = default для таких деструкторов. Особенно, если вы просто заменили его на {}. Я думаю, что = default лучше, потому что он более явный, поэтому он сразу бросается в глаза и не оставляет места для сомнений.

Однако при этом нужно учитывать несколько заметок.

Когда вы = default деструктор в файле заголовка (см. править) (или любая другая специальная функция, если на то пошло), он в основном определяет его в заголовке. При разработке общей библиотеки вы можете явно указать деструктор, предоставляемый только библиотекой, а не в заголовке, чтобы вы могли легче изменить ее в будущем, не требуя пересоздания зависимого двоичного файла. Но опять же, когда вопрос заключается не только в = default или {}.


РЕДАКТИРОВАТЬ:. Как замечает Шон в комментариях, вы также можете использовать = default вне декларации класса, который получает лучшее из обоих миров здесь.


Другим важным техническим различием является то, что стандарт говорит о том, что явно дефолтная функция, которая не может быть сгенерирована, просто не будет сгенерирована. Рассмотрим следующий пример:

struct A { ~A() = delete; };
struct B : A { ~B() {}; }

Это не скомпилировалось бы, поскольку вы вынуждаете компилятор генерировать указанный код (и его неявные реквизиты, такие как вызов деструктора) для деструктора B, и он не может, поскольку деструктор удален. Однако рассмотрим это:

struct A { ~A() = delete; };
struct B : A { ~B() = default; }

Это, по сути, будет компилироваться, потому что компилятор видит, что ~B() не может быть сгенерирован, поэтому он просто не генерирует его - и объявляет его как удаленный. Это означает, что вы получите только ошибку, когда пытаетесь фактически использовать/вызывать B::~B().

Это, по крайней мере, два значения, о которых вы должны знать:

  • Если вы хотите получить ошибку, просто компилируя что-либо, содержащее объявление класса, вы не получите его, так как оно технически корректно.
  • Если вы изначально всегда используете = default для таких деструкторов, вам не придется беспокоиться о том, что удаляемый деструктор суперкласса удаляется, если он окажется в порядке, и вы никогда его не используете. Это своего рода экзотическое использование, но в этом смысле оно более корректно и надежно. Вы получите только ошибку, если вы действительно используете деструктор, иначе вы останетесь один.

Итак, если вы собираетесь использовать защитный подход к программированию, вам может потребоваться просто использовать {}. В противном случае вы, вероятно, лучше = default ing, так как это лучше соответствует минимальным программным инструкциям, необходимым для получения правильной рабочей базы и предотвращения непреднамеренных последствий 1.


Что касается = 0: Да, это все еще правильный способ сделать это. Но учтите, что на самом деле он не указывает, что "нет реализации по умолчанию", а скорее, что (1) класс не является исполняемым; и (2) Любые производные классы должны переопределить эту функцию (хотя они могут использовать необязательную реализацию, предоставляемую суперклассом). Другими словами, вы можете определить функцию и объявить ее чистым виртуальным. Вот пример:

struct A { virtual ~A() = 0; }
A::~A() = default;

Это обеспечит эти ограничения для A (и его деструктора).


1) Хорошим примером того, почему это может быть полезно неожиданным образом, является то, как некоторые люди всегда использовали return с круглыми скобками, а затем С++ 14 добавил decltype(auto), который по существу был создан техническая разница между его использованием и без круглых скобок.