С++ "Виртуальные функции, но не виртуальные деструкторы"

У меня есть базовый класс Media и несколько производных классов, а именно DVD, Book и т.д. Базовый класс записывается как:

class Media{
    private:
        int id;
        string title;
        int year;
    public:
        Media(){ id = year = 0; title = ""; }
        Media(int _id, string _title, int _year): id(_id), title(_title), year(_year) {}
//      virtual ~Media() = 0;
        void changeID(int newID){ id = newID; }
        virtual void print(ostream &out);
};

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

undefined ссылка на Media::~Media()

Создание виртуального виртуального деструктора не решает проблему. Итак, что пошло не так?

Ответы

Ответ 1

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

//Media.h
class Media{
    //....
    virtual ~Media() = 0;
};

//Media.cpp
#include "Media.h"
//....
Media::~Media() {};

Причина, по которой вы получаете предупреждения, состоит в том, что все классы, которые должны быть получены, должны иметь виртуальный или защищенный (credit @Steve) деструктор, в противном случае удаление экземпляра с помощью указателя на базовый класс приведет к поведению undefined.

Примечание. У вас есть определение деструкторов, даже если они являются чистыми виртуальными.

Ответ 2

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

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

EDIT: более общее решение, в ответ на комментарий LuchianGrigore (спасибо, что указали)

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

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

Ответ 3

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

Это раздражающее предупреждение в Modern С++, но в старом объектно-стиле С++ оно обычно корректно.

Проблема заключается в том, как разрушаются ваши объекты. Простой тест:

#include <iostream>

class Base {};
class Derived: public Base { public: ~Derived() { std::cout << "Aargh\n"; } };

int main() {
  Base* b = new Derived();
  Derived* d = new Derived();

  delete d;
  delete b;
}

Отпечатки:

Aargh

Да, только один раз.

Проблема заключается в том, что при вызове delete для переменной типа Base* вызывается метод Base::~Base(). Если это virtual, тогда вызов динамически отправляется в окончательный метод (на основе динамического типа), в этом случае Derived::~Derived(), но если это не так, то Derived::~Derived() никогда не вызывается, поэтому никогда не выполняется.

Поэтому, если вы хотите вызвать delete (или использовать интеллектуальные указатели, которые делают это для вас) в базовых типах, вам нужно добавить virtual ~Base() {} в свои определения классов. Вот почему gcc предупреждает вас, когда вы создаете полиморфный класс без деструктора virtual.


Примечание: время изменилось, и с тех пор я реализовал -Wdelete-non-virtual-dtor в Clang, и он также был реплицирован в gcc.

-Wnon-virtual-dtor полезен для библиотек (как он предупреждает о базовом классе), но может иметь более высокую ложную положительную скорость; с другой стороны -Wdelete-non-virtual-dtor срабатывает на сайте вызова и имеет значительно более низкие значения ложных срабатываний (которые вы обычно можете обойти, используя peppering final, чтобы удалить "полиморфное" свойство класса).

Ответ 4

То, что вы закомментировали, - это чисто виртуальное объявление для деструктора. Это означает, что функция должна быть переопределена в производном классе, чтобы иметь возможность создавать экземпляр объекта этого класса.

То, что вы хотите, - это просто определение деструктора как виртуальной функции:

virtual ~Media() {}

Ответ 5

Объявление об отказе сначала, а затем Попробуйте добавить следующую строку после объявления класса

Media::~Media(){}