Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 30 di 93
  • livello avanzato
Indice lezioni

Distruttore Virtuale

Imparare a conoscere ed utilizzare i distruttori virtuali, costrutti essenziali per l'implementazione di gerarchie di classi in linguaggio C++.
Imparare a conoscere ed utilizzare i distruttori virtuali, costrutti essenziali per l'implementazione di gerarchie di classi in linguaggio C++.
Link copiato negli appunti

Nelle lezioni precedenti abbiamo parlato del ruolo di costruttori e distruttori nel ciclo di vita delle istanze di una classe.

Il distruttore, in particolare, svolge un ruolo importante nel mantenere consistente e coerente lo stato del programma dopo la distruzione di un oggetto, poiché, se correttamente implementato, rilascia tutte le risorse ad esso associate quali memoria, file, mutex, eccetera, rendendole riutilizzabili.

Normalmente, quando si definisce una gerarchia ci classi, l'invocazione del distruttore di una classe appartenente a qualsiasi livello intermedio della gerarchia, provoca l'invocazione in sequenza dei distruttori delle classi base, in ordine inverso a quello di derivazione.

Come esempio, nel listato seguente si riporta la definizione di una classe base e di una sua derivata. Nel distruttore di entrambe è presente una serie di istruzioni che hanno il solo effetto collaterale di dare traccia dell'invocazione del metodo nell'output del programma.

Nel corpo della funzione main() è inserito il codice per l'allocazione dinamica di un'istanza della classe derivata derv_ptr, e la sua successiva distruzione.

#include <iostream>
using namespace std;
class Base
{
public:
Base() {}
~Base()
{
std::cout << "Distruttore della classe Base" << std::endl;
}
};
class Derived : public Base
{
public:
Derived() : Base ()
{
ptr = new int;
*ptr = 123456789;
}
~Derived()
{
std::cout << "Distruttore della classe Derived" << std::endl;
delete ptr;
}
int* ptr;
};
int main()
{
Derived *derv_ptr = new Derived();
std::cout << "Distruzione di derv_ptr" << std::endl;
delete derv_ptr;
}

L'output prodotto dall'esecuzione di questo programma, che è riportato nel seguito, mostra l'invocazione in sequenza dei distruttori della classe derivata e della classe base.

Distruzione di derv_ptr:
Distruttore della classe Derived
Distruttore della classe Base

Questo meccanismo consente di distribuire alle varie classi di competenza, sui livelli della gerarchia, il codice necessario per la distruzione di un'istanza aggregata mediante ereditarietà.

Tuttavia, quando entra in gioco il comportamento polimorfico degli oggetti, lo specificatore virtual è fondamentale nella definizione del distruttore di una classe base, al fine di preservare questo meccanismo.

Nel listato successivo, supponendo di mantenere inalterata la definizione delle classi Base e Derived, il corpo della funzione main() viene modificato con l'inserimento del codice necessario per l'allocazione dinamica di un'altra istanza della classe derivata che viene referenziata dal puntatore alla classe base base_ptr, in osservanza del comportamento polimorfico degli oggetti in C++.

int main()
{
Base *base_ptr = new Derived(); // <== inizializzazione "polimorfica" di un puntatore alla classe base
std::cout << "Distruzione di base_ptr" << std::endl;
delete base_ptr;
Derived *derv_ptr = new Derived();
std::cout << "Distruzione di derv_ptr" << std::endl;
delete derv_ptr;
}

Nell'output prodotto dall'esecuzione di questo programma di esempio, che è riprodotto nel seguito, è possibile osservare una differenza sostanziale nel modo in cui le istanze vengono distrutte, anche se esse appartengono alla medesima classe.

Distruzione di base_ptr:
Distruttore della classe Base
Distruzione di derv_ptr:
Distruttore della classe Derived
Distruttore della classe Base

Nella distruzione dell'istanza referenziata mediante un puntatore alla classe base non vi è traccia dell'invocazione del distruttore della classe derivata, cosa che invece avviene per la seconda istanza, dove correttamente vengono invocati in sequenza il distruttore della classe derivata e quello della classe base.

Questa incoerenza è dovuta al fatto che il distruttore della classe base non è un metodo virtuale e pertanto alla sua invocazione non corrisponde l'associazione dinamica al distruttore dell'istanza puntata da base_ptr, cioè ~Derived().

Nel caso in esame quindi il distruttore invocato è quello che appartiene nominalmente al tipo che viene usato per referenziare l'istanza, cioè la classe Base, per la quale tra l'altro non è definita alcuna tabella dei metodi virtuali poiché in essa non è definito nessun metodo virtuale.

Come conseguenza di ciò, nel caso specifico, la distruzione dell'istanza referenziata da base_ptr produce un memory leak poiché, non essendo invocato il distruttore ~Derived(), la memoria puntata dal membro di classe ptr non viene deallocata.

La soluzione a questo problema consiste nel definire il distruttore della classe base con l'uso dello specificatore virtual, come riportato nel listato seguente:

#include <iostream>
using namespace std;
class Base
{
public:
Base() {}
virtual ~Base()
{
std::cout << "Distruttore della classe Base" << std::endl;
}
};
class Derived : public Base
{
public:
Derived() : Base ()
{
ptr = new int;
*ptr = 123456789;
}
virtual ~Derived()
{
std::cout << "Distruttore della classe Derived" << std::endl;
delete ptr;
}
int* ptr;
};
int main()
{
Base *base_ptr = new Derived(); // <== inizializzazione "polimorfica" di un puntatore alla classe base
std::cout << "Distruzione di base_ptr" << std::endl;
delete base_ptr;
Derived *derv_ptr = new Derived();
std::cout << "Distruzione di derv_ptr" << std::endl;
delete derv_ptr;
}

All'esecuzione di quest'ultimo listato, corrisponderà l'output seguente, dove si osserva un comportamento coerente per entrambe le istanze, e soprattutto corretto dal punto di vista formale.

Distruzione di base_ptr:
Distruttore della classe Derived
Distruttore della classe Base
Distruzione di derv_ptr:
Distruttore della classe Derived
Distruttore della classe Base

Si osservi che lo specificatore virtual associato al distruttore della classe base si propaga automaticamente a tutte le classi derivate, anche a quello di default generato dal compilatore. Pertanto non è necessario che esso compaia nella definizione dei distruttori delle classi derivate, anche se è preferibile al fine di per preservare la leggibilità del codice.

Ti consigliamo anche