2008-11-07 2 views
138

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

ответ

166

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

Например

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; 
} 
+4

`delete p` вызывает неопределенное поведение. Нельзя называть интерфейс `Interface :: ~ Interface`. – Mankarse 2012-01-23 11:04:28

+0

@Mankarse: можете ли вы объяснить, почему он не определен? Если Derived не реализовал свой собственный деструктор, неужели это будет неопределенное поведение? – Ponkadoodle 2012-03-20 00:36:59

+12

@Wallacoloo: Не определено из-за `[expr.delete] /`: `... если статический тип объекта, который нужно удалить, отличается от его динамического типа ... статический тип должен иметь виртуальный деструктор или поведение не определено. ... `. Он все равно не определен, если Derived использовал неявно созданный деструктор. – Mankarse 2012-03-20 00:46:48

6

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

Таким образом, вы открываете возможности для утечки памяти

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 
+0

Правда, на самом деле в этом примере это может быть не просто утечка памяти, но, возможно, авария: -/ – 2008-11-07 01:05:22

+1

Зачем это крушить? – 2008-11-07 11:35:32

5

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

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

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

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

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

+0

Без обид. Я думал, что вы сделали несколько хороших моментов и подумали, что более прямой вопрос обеспечит такую ​​же помощь кому-то еще. Благодарю. – Kevin 2008-11-07 01:06:58

5

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

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

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

Animal* pAnimal = GetAnimal(); 
delete pAnimal; 

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

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

34

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

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

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

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

20

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

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

Я надеюсь, что это помогает.

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

Ссылки:

3

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

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

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