Почему я должен объявлять виртуальный деструктор для абстрактного класса в C ++? - PullRequest
156 голосов
/ 07 ноября 2008

Я знаю, что это хорошая практика - объявлять виртуальные деструкторы для базовых классов в C ++, но всегда ли важно объявлять virtual деструкторы даже для абстрактных классов, которые функционируют как интерфейсы? Пожалуйста, укажите некоторые причины и примеры, почему.

Ответы [ 7 ]

182 голосов
/ 07 ноября 2008

Это еще более важно для интерфейса. Любой пользователь вашего класса, вероятно, будет содержать указатель на интерфейс, а не указатель на конкретную реализацию. Когда они приходят, чтобы удалить его, если деструктор не виртуальный, они будут вызывать деструктор интерфейса (или предоставленный компилятором по умолчанию, если вы его не указали), а не деструктор производного класса. Мгновенная утечка памяти.

Например

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived() 
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived
   delete p; 
}
37 голосов
/ 07 ноября 2008

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

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

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

[См. Пункт 4 в этой статье: http://www.gotw.ca/publications/mill18.htm]

22 голосов
/ 09 июля 2011

Я решил провести небольшое исследование и попытаться обобщить ваши ответы. Следующие вопросы помогут вам решить, какой деструктор вам нужен:

  1. Ваш класс предназначен для использования в качестве базового класса?
    • No: объявлять открытый не виртуальный деструктор, чтобы избежать v-указателя на каждый объект класса *.
    • Да: прочитайте следующий вопрос.
  2. Является ли ваш базовый класс абстрактным? (то есть какие-нибудь виртуальные чистые методы?)
    • Нет: попробуйте сделать базовый класс абстрактным, изменив иерархию классов
    • Да: прочитайте следующий вопрос.
  3. Хотите разрешить полиморфное удаление через базовый указатель?
    • Нет: объявить защищенный виртуальный деструктор для предотвращения нежелательного использования.
    • Да: объявить общедоступный виртуальный деструктор (в этом случае нет накладных расходов).

Надеюсь, это поможет.

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

Ссылки

7 голосов
/ 07 ноября 2008

Это не всегда требуется, но я считаю, что это хорошая практика. Он позволяет безопасно удалять производный объект через указатель базового типа.

Так, например:

Base *p = new Derived;
// use p as you see fit
delete p;

плохо сформирован, если Base не имеет виртуального деструктора, потому что он попытается удалить объект, как если бы это был Base *.

7 голосов
/ 07 ноября 2008

Да, это всегда важно. Производные классы могут выделять память или содержать ссылку на другие ресурсы, которые необходимо будет очистить при уничтожении объекта. Если вы не передадите своим интерфейсам / абстрактным классам виртуальные деструкторы, то каждый раз, когда вы удаляете экземпляр производного класса через дескриптор базового класса, деструктор вашего производного класса не будет вызываться.

Следовательно, вы открываете потенциал для утечек памяти

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
5 голосов
/ 07 ноября 2008

Это не только хорошая практика. Это правило № 1 для любой иерархии классов.

  1. Самый базовый класс иерархии в C ++ должен иметь виртуальный деструктор

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

Animal* pAnimal = GetAnimal();
delete pAnimal;

Предположим, что Animal является абстрактным классом. Единственный способ, которым C ++ знает правильный деструктор для вызова, - это диспетчеризация виртуального метода. Если деструктор не является виртуальным, он просто вызывает деструктор Animal и не уничтожает объекты в производных классах.

Причина создания виртуального деструктора в базовом классе заключается в том, что он просто удаляет выбор из производных классов. Их деструктор становится виртуальным по умолчанию.

3 голосов
/ 27 декабря 2012

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

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

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

...