Risposte:
È ancora più importante per un'interfaccia. Qualsiasi utente della tua classe probabilmente terrà un puntatore all'interfaccia, non un puntatore all'implementazione concreta. Quando arrivano per eliminarlo, se il distruttore non è virtuale, chiamano il distruttore dell'interfaccia (o il predefinito fornito dal compilatore, se non ne hai specificato uno), non il distruttore della classe derivata. Perdita di memoria istantanea.
Per esempio
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. ...
. Sarebbe ancora indefinito se Derived usasse un distruttore generato implicitamente.
La risposta alla tua domanda è spesso, ma non sempre. Se la tua classe astratta vieta ai clienti di chiamare delete su un puntatore (o se lo dice nella sua documentazione), sei libero di non dichiarare un distruttore virtuale.
Puoi vietare ai client di chiamare delete su un puntatore ad esso proteggendo il suo distruttore. Funzionando in questo modo, è perfettamente sicuro e ragionevole omettere un distruttore virtuale.
Alla fine finirai con nessuna tabella dei metodi virtuali e finirai per segnalare ai tuoi clienti la tua intenzione di renderlo non cancellabile tramite un puntatore ad esso, quindi hai davvero motivo di non dichiararlo virtuale in quei casi.
[Vedi l'articolo 4 in questo articolo: http://www.gotw.ca/publications/mill18.htm ]
Ho deciso di fare qualche ricerca e provare a sintetizzare le tue risposte. Le seguenti domande ti aiuteranno a decidere di quale tipo di distruttore hai bisogno:
Spero che aiuti.
* È importante notare che in C ++ non c'è modo di contrassegnare una classe come finale (cioè non sottoclassabile), quindi nel caso in cui decidi di dichiarare il tuo distruttore non virtuale e pubblico, ricorda di mettere in guardia esplicitamente i tuoi colleghi programmatori contro derivante dalla tua classe.
Riferimenti:
Sì, è sempre importante. Le classi derivate possono allocare memoria o contenere riferimenti ad altre risorse che dovranno essere ripulite quando l'oggetto viene distrutto. Se non si assegnano distruttori virtuali alle interfacce / classi astratte, ogni volta che si elimina un'istanza di classe derivata tramite un handle di classe base, il distruttore della classe derivata non verrà chiamato.
Quindi, stai aprendo il potenziale per perdite di memoria
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
Non è sempre necessario, ma trovo che sia una buona pratica. Ciò che fa è che consente a un oggetto derivato di essere eliminato in modo sicuro tramite un puntatore di un tipo base.
Quindi per esempio:
Base *p = new Derived;
// use p as you see fit
delete p;
è mal formato se Base
non ha un distruttore virtuale, perché tenterà di eliminare l'oggetto come se fosse un Base *
.
shared_ptr
tenterà di eliminare l'oggetto come se fosse un Base *
- ricorda il tipo di cosa con cui lo hai creato. Vedi il link di riferimento, in particolare il bit che dice "Il distruttore chiamerà delete con lo stesso puntatore, completo del suo tipo originale, anche quando T non ha un distruttore virtuale o è nullo".
Non è solo una buona pratica. È la regola n. 1 per qualsiasi gerarchia di classi.
Ora per il perché. Prendi la tipica gerarchia animale. I distruttori virtuali passano attraverso l'invio virtuale proprio come qualsiasi altra chiamata di metodo. Prendi il seguente esempio.
Animal* pAnimal = GetAnimal();
delete pAnimal;
Supponiamo che Animal sia una classe astratta. L'unico modo in cui C ++ conosce il distruttore corretto da chiamare è tramite l'invio di metodi virtuali. Se il distruttore non è virtuale, chiamerà semplicemente il distruttore di animali e non distruggerà alcun oggetto nelle classi derivate.
Il motivo per rendere virtuale il distruttore nella classe base è che rimuove semplicemente la scelta dalle classi derivate. Il loro distruttore diventa virtuale per impostazione predefinita.
La risposta è semplice, è necessario che sia virtuale, altrimenti la classe base non sarebbe una classe polimorfica completa.
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
Preferiresti la cancellazione di cui sopra, ma se il distruttore della classe base non è virtuale, verrà chiamato solo il distruttore della classe base e tutti i dati nella classe derivata rimarranno non cancellati.
delete p
invoca un comportamento indefinito. Non è garantito chiamareInterface::~Interface
.