Когда и почему деструктор в базовом классе НЕ определяется как виртуальный?

В этом примере ниже показано, как предотвратить копирование производного класса. Он основан на базовом классе, где объявлены как конструктор копирования, так и оператор назначения копии private.

class Uncopyable
{
protected:
   // allow construction and destruction of derived objects...
   Uncopyable() {}
   ~Uncopyable() {}

private:
   // but prevent copying...
   Uncopyable(const Uncopyable&);
   Uncopyable& operator=(const Uncopyable&);
};

Мы можем использовать этот класс в сочетании с частным наследованием, чтобы сделать классы недоступными:

class derived: private Uncopyable
{...};

Обратите внимание, что деструктор в классе Uncopyable не объявлен как virtual.

Раньше я узнал, что

  • Деструкторы в базовых классах должны быть virtual.
  • Деструкторы в не-базовых классах не должны быть сделаны virtual.

В этом примере деструктор для Uncopyable не virtual, но он унаследован. Это, похоже, противоречит мудрости, которую я изучил ранее.

Когда и почему деструктор в базовом классе НЕ определяется как virtual?

Ответ 1

Деструктор базового класса должен быть virtual, если вы можете попытаться освободить объект производного типа с помощью указателя базового типа. Следовательно, если вы только наследуете базовый класс конфиденциально, а не публично, как это было бы в Uncopyable, тогда вам не нужно беспокоиться о вводе деструктора virtual, потому что при использовании частного наследования вы можете " t получить указатель на производный объект и сохранить его в указателе на базовый тип.

Другим примером может быть, если вы должны использовать класс mixin, такой как этот, который заставляет класс отслеживать количество распределений объектов, где mixin унаследован, чтобы получить поведение, но не обрабатываться полиморфно:

template <typename T> class Counter {
public:
    Counter() { ++numInstances; }
    Counter(const Counter&) { ++numInstances );
    ~Counter() { --numInstances; }

    static unsigned getNumInstances() { return numInstances; }

private:
    static unsigned numInstances;
}
template <typename T> unsigned Counter<T>::numInstances = 0;

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

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

Ответ 2

Когда вы создаете базу не как интерфейс, а как деталь реализации (обратите внимание на наследование private от Uncopyable).

Ответ 3

Технически вам не нужно делать виртуальный виртуальный дескриптор, если вы знаете, что никто не удалит его как Uncopyable *, но всегда будет удалять его как подкласс того же самого.

Да, это, по сути, то, что сказал @templatetypedef, но я собираюсь объяснить это, возможно, проще.

Итак: если люди могут сделать что-то вроде этого:

void aFunction(Uncopyable* obj) {
    delete obj;
}

Затем вы должны объявить виртуальный деструктор (чтобы потенциальные подклассы получили их деструктор.

Однако, если вы знаете, что люди будут удалять подклассы, например:

class Widget : public Uncopyable
{
  ....
};

void aFunction(Widget* obj) {
   delete obj;
}

Тогда вам не нужно делать ваш деструктор виртуальным (так как вызывается деструктор подклассов).

По крайней мере, это мое понимание.

Ответ 4

Если вам нужны ваши старые данные, без vtable. Я бы прокомментировал это, если бы мне это понадобилось, поскольку 99% времени, оставив "виртуальный" в деструкторах базового класса, просто ошибка, которую кто-то захочет исправить.

Ответ 5

Общее правило - сделать ваш деструктор общедоступным и виртуальным, или защищенным, и не виртуальным. В первом случае ваш объект использует уничтожаемый полиморфно, а виртуальный деструктор будет делать правильные вещи. Во втором случае он будет уничтожен только как фактический класс ребенка и по-прежнему будет правильным.