Ответы:
Это еще более важно для интерфейса. Любой пользователь вашего класса, вероятно, будет содержать указатель на интерфейс, а не указатель на конкретную реализацию. Когда они приходят, чтобы удалить его, если деструктор не виртуальный, они будут вызывать деструктор интерфейса (или предоставленный компилятором по умолчанию, если вы его не указали), а не деструктор производного класса. Мгновенная утечка памяти.
Например
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;
}
[expr.delete]/
: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...
. Было бы все еще неопределенным, если бы Derived использовал неявно сгенерированный деструктор.
Ответ на ваш вопрос часто, но не всегда. Если ваш абстрактный класс запрещает клиентам вызывать delete по указателю на него (или если он говорит об этом в своей документации), вы можете не объявлять виртуальный деструктор.
Вы можете запретить клиентам вызывать delete для указателя, сделав его деструктором защищенным. Работая так, совершенно безопасно и разумно опустить виртуальный деструктор.
В конечном итоге у вас не останется таблицы виртуальных методов, и в итоге вы дадите сигнал своим клиентам о своем намерении сделать ее не удаляемой через указатель на нее, поэтому у вас действительно есть причина не объявлять ее виртуальной в этих случаях.
[См. Пункт 4 в этой статье: http://www.gotw.ca/publications/mill18.htm ]
Я решил провести небольшое исследование и попытаться обобщить ваши ответы. Следующие вопросы помогут вам решить, какой деструктор вам нужен:
Надеюсь, это поможет.
* Важно отметить, что в C ++ нет способа пометить класс как окончательный (т.е. не подклассифицируемый), поэтому в случае, если вы решите объявить свой деструктор не виртуальным и общедоступным, не забывайте явно предупреждать ваших коллег-программистов против исходя из вашего класса.
Ссылки:
Да, это всегда важно. Производные классы могут выделять память или содержать ссылку на другие ресурсы, которые необходимо будет очистить при уничтожении объекта. Если вы не передадите своим интерфейсам / абстрактным классам виртуальные деструкторы, то каждый раз, когда вы удаляете экземпляр производного класса через дескриптор базового класса, деструктор вашего производного класса не будет вызываться.
Следовательно, вы открываете потенциал для утечек памяти
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
Это не всегда требуется, но я считаю, что это хорошая практика. Что он делает, так это позволяет безопасно удалить производный объект через указатель базового типа.
Так, например:
Base *p = new Derived;
// use p as you see fit
delete p;
плохо сформирован, если Base
не имеет виртуального деструктора, потому что он попытается удалить объект, как если бы он был Base *
.
shared_ptr
он пытается удалить объект, как если бы он был Base *
- он запоминает тип того, с чем вы его создали. См. Ссылочную ссылку, в частности, бит, который говорит: «Деструктор будет вызывать delete с тем же указателем, в комплекте с его исходным типом, даже если у T нет виртуального деструктора или он пуст».
Это не только хорошая практика. Это правило № 1 для любой иерархии классов.
Теперь почему. Возьмите типичную иерархию животных. Виртуальные деструкторы проходят виртуальную диспетчеризацию, как и любой другой вызов метода. Возьмите следующий пример.
Animal* pAnimal = GetAnimal();
delete pAnimal;
Предположим, что Animal является абстрактным классом. Единственный способ, которым C ++ знает правильный деструктор для вызова, - это диспетчеризация виртуального метода. Если деструктор не является виртуальным, он просто вызывает деструктор Animal и не уничтожает объекты в производных классах.
Причина создания виртуального деструктора в базовом классе заключается в том, что он просто удаляет выбор из производных классов. Их деструктор становится виртуальным по умолчанию.
Ответ прост, вам нужно, чтобы он был виртуальным, иначе базовый класс не был бы полным полиморфным классом.
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
Вы бы предпочли удаление, описанное выше, но если деструктор базового класса не является виртуальным, будет вызван только деструктор базового класса, и все данные в производном классе останутся восстановленными.
delete p
вызывает неопределенное поведение. Звонить не гарантируетсяInterface::~Interface
.