Respostas:
É ainda mais importante para uma interface. Qualquer usuário da sua classe provavelmente conterá um ponteiro para a interface, não um ponteiro para a implementação concreta. Quando eles vierem para excluí-lo, se o destruidor não for virtual, eles chamarão o destruidor da interface (ou o padrão fornecido pelo compilador, se você não especificou um), não o destruidor da classe derivada. Vazamento de memória instantânea.
Por exemplo
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. ...
. Ainda seria indefinido se Derived usasse um destruidor gerado implicitamente.
A resposta para sua pergunta é frequentemente, mas nem sempre. Se a sua classe abstrata proíbe que os clientes chamem delete em um ponteiro para ele (ou, se assim o desejar na documentação), você é livre para não declarar um destruidor virtual.
Você pode proibir que os clientes chamem delete em um ponteiro para ele, protegendo seu destruidor. Trabalhando assim, é perfeitamente seguro e razoável omitir um destruidor virtual.
Você acabará por não ter uma tabela de método virtual e sinalizará a seus clientes sua intenção de torná-la não excluída por meio de um ponteiro para ela, portanto, você tem motivos para não declara-la virtual nesses casos.
[Veja o item 4 deste artigo: http://www.gotw.ca/publications/mill18.htm ]
Decidi fazer uma pesquisa e tentar resumir suas respostas. As seguintes perguntas ajudarão você a decidir de que tipo de destruidor você precisa:
Eu espero que isso ajude.
* É importante observar que no C ++ não há como marcar uma classe como final (ou seja, não subclassável), portanto, caso decida declarar seu destruidor não virtual e público, lembre-se de alertar explicitamente seus colegas programadores contra derivado de sua classe.
Referências:
Sim, é sempre importante. Classes derivadas podem alocar memória ou manter referência a outros recursos que precisarão ser limpos quando o objeto for destruído. Se você não atribuir destruidores virtuais de suas interfaces / classes abstratas, toda vez que você excluir uma instância de classe derivada por meio de um identificador de classe base, o destruidor de sua classe derivada não será chamado.
Portanto, você está abrindo o potencial de vazamentos de memória
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
Nem sempre é necessário, mas acho que é uma boa prática. O que ele faz é que ele permite que um objeto derivado seja excluído com segurança por meio de um ponteiro do tipo base.
Então, por exemplo:
Base *p = new Derived;
// use p as you see fit
delete p;
está mal formado se Base
não tiver um destruidor virtual, porque tentará excluir o objeto como se fosse um Base *
.
shared_ptr
tentará excluir o objeto como se fosse um Base *
- ele se lembra do tipo de coisa com a qual você o criou. Consulte o link referenciado, em particular o bit que diz "O destruidor chamará delete com o mesmo ponteiro, completo com seu tipo original, mesmo quando T não tiver um destruidor virtual ou for nulo".
Não é apenas uma boa prática. É a regra nº 1 para qualquer hierarquia de classes.
Agora, o porquê. Tome a hierarquia animal típica. Os destruidores virtuais passam pelo despacho virtual, assim como qualquer outra chamada de método. Veja o exemplo a seguir.
Animal* pAnimal = GetAnimal();
delete pAnimal;
Suponha que Animal é uma classe abstrata. A única maneira pela qual o C ++ conhece o destruidor adequado a ser chamado é através do envio de método virtual. Se o destruidor não for virtual, ele simplesmente chamará o destruidor de Animal e não destruirá nenhum objeto nas classes derivadas.
A razão para tornar o destruidor virtual na classe base é que ele simplesmente remove a escolha das classes derivadas. Seu destruidor se torna virtual por padrão.
A resposta é simples: você precisa que ela seja virtual, caso contrário, a classe base não seria uma classe polimórfica completa.
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
Você prefere a exclusão acima, mas se o destruidor da classe base não for virtual, apenas o destruidor da classe base será chamado e todos os dados na classe derivada permanecerão desmarcados.
delete p
invoca comportamento indefinido. Não é garantido ligarInterface::~Interface
.