Cerca nel blog

Loading

4 febbraio 2008

Smart pointer: come evitare i memory leak

Vi ricordate l'ultimo post relativo alla programmazione? Vi avevo promesso una spiegazione piu' dettagliata su come evitare memory leaks ed eccomi qui a mantenere la promessa.

Possiamo definire il memory leak come un errore di programmazione in cui a seguito di una assegnazione dinamica di memoria nello heap non vi sia una corrispondente "liberazione" della stessa. Forse e' meglio fare qualche esempio:

void funzioneLeaky() {

   MyClass * miaClasse = new MyClass;

   // fai qualcosa e poi ritorna
   return;
}

void funzioneNonLeaky() {

   MyClass * miaClasse = new MyClass;

   // fai qualcosa ma prima di ritornare ricordati di "liberare"
   // mia classe
   delete miaClasse;

   // non e' necessario, ma utile
   miaClasse = 0x0;

   return;
}


La differenza tra le due implementazioni e' evidente, talmente chiara che potreste essere portati a credere che commettere un tale errore sia praticamente impossibile. Ma non e' proprio cosi'!

Ci sono, infatti, delle situazioni in cui anche se il codice e' scritto come nel caso della funzione non leaky, l'esecuzione del programma ha effettivamente degli sprechi di memoria. Ma come e' possibile? La spiegazione e' semplice, fintanto che l'implementazione del codice e' solo di poche righe allora non ci saranno problemi, ma quando le cose si fanno piu' complesse allora e' possibile che la funzione ritorni prima della chiamata a delete, oppure che una istruzioni lanci una exception (questa sara' l'argomento di un'altra lezione) e quindi termini inaspettatamente senza avere la possibilita' di liberare la memoria. Una possibile via d'uscita esiste e si basa sull'uso dei cosiddetti smart pointers, ovvero dei puntatori creati dinamicamente che vengono automaticamente distrutti non appena escono fuori di scope, come avviene per le variabili nello stack.

Tra tutte le implementazioni di smart pointers, quella piu' comunemente usata e' auto_ptr essendo disponibile nelle librerie C++ standard e denifinata nell'include memory. Ecco come il codice della nostra funzione sarebbe usando l'auto pointer:

void funzioneAutoPointer() {

   auto_ptr<MyClass> miaClasse( new MyClass );

   // fai tutto quello che devi. Puoi usare i metodi di miaClasse
   // come se fosse un normale puntatore, per esempio:
   miaClasse->FaiQualcosa();

   // ritorna e non ti preoccupare di liberare la memoria tanto
   // verra' automaticamente liberata.
   return;
}

Come funziona l'auto_ptr?

auto_ptr e' una classe template come avrete notato dall'uso delle parentesi angolari <>. Il costruttore dell'auto pointer prende come argomento un puntatore ad un oggetto della classe specificata nel template e lo assegna ad un membro data; inoltre viene ridefinito l'operatore "->" in modo che tutti i metodi della classe MyClass vengano applicati direttamente al membro data.
L'auto pointer viene generalmente costruito sullo stack, quindi il suo distruttore viene automaticamente chiamato non appena va fuori scope. Ovviamente l'implementazione del distruttore e' tale che viene distrutto il membro data associato all'auto pointer, liberando completamente la memoria occupata.
In altre parole, nella nostra funzione, qualunque cosa succeda dopo la creazione di miaClasse, la memoria ad essa associata verra' completamente liberata. Geniale vero?

Ma se non volessi cancellare la memoria?

Nell'esempio precedente, l'oggetto miaClasse veniva temporaneamente utilizzato solo all'interno della funzione, quindi e' doveroso distruggerlo al termine dell'esecuzione. Esisto pero' dei casi in cui un oggetto viene creato all'interno della funzione, ma lo vogliamo tenere vivo anche al termine della funzione e continuare ad utilizzarlo. Allora la domanda e': come possiamo usare auto_ptr in quel caso? Vediamo un esempio:

// funzione ritorna un puntatore a MyClass
MyClass * funzione() {

   auto_ptr<MyClass> miaClasse( new MyClass );

   // fai qualcosa con mia classe
   miaClasse->FaiQualocosa();

   // e poi ritorna il puntatore al nuovo oggetto
   // senza distruggerlo

   return miaClasse.release();
}


Nel codice qui sopra riportato, un auto pointer viene creato e gli viene assegnata un nuovo oggetto di tipo MyClass. La proprieta' di questo nuovo oggetto e quindi l'implicita responsabilita' di distruggerlo e' dell'auto pointer fino all'ultima riga dove viene chiamato il metodo release. Con questa chiamata, l'auto pointer viene liberato della proprieta' del nuovo oggetto e quando miaClasse verra' distrutto il nuovo oggetto continuera' ad esistere e (si spera) che verra' distrutto dal nuovo proprietario.
Notate come la chiamata a release avvenga con l'operatore "." e non "->". Possiamo interpretarlo nel modo seguente: per accedere ai metodi della classe auto_ptr (come release(), get(),...) si deve usare l'operatore ".", mentre per accedere a quelli dell'oggetto puntato si usa l'operatore "->" come appare naturale per un puntatore.

Ma l'auto_ptr e' o non e' un puntatore?

La risposta e' chiaramente no, anche se contiene il puntatore all'oggetto che abbiamo creato. Questa sottigliezza e' alla base di parecchi errori di compilazione quando una funzione richiede come argomento un puntatore e il programmatore gli passa un auto_ptr. Vediamo un esempio anche per questo caso:

void altraFunzione(MyClass * argomento) {

   argomento->FaiQualcosa();

}

void funzione() {

   auto_ptr<MyClass> miaClasse(new MyClass );

   // chiama altraFunzione, passando il puntatore non l'auto pointer!
   altraFunzione( miaClasse.get() );

return ;

}

L'auto pointer non e' un puntatore! Se vogliamo usare il puntatore all'oggetto associato all'auto pointer dobbiamo usare il metodo get(), attraverso l'operatore ".". Attenzione che il metodo get() lascia la proprieta' del puntatore all'auto pointer e quindi la memoria verra' liberata quando l'auto pointer viene distrutto.

Ma allora perche' non usare sempre gli auto pointer?

Bella domanda! Infatti dovremmo sempre utilizzare gli auto pointer, ma ci sono delle importanti limitazioni da tenere in considerazione, in particolare per quanto riguarda la creazione di copie. Il messaggio generale e' che per gli auto pointer le copie non sono equivalenti! Per chi non se fosse reso conto, questo ha delle pesate implicazioni quando gli auto pointers sono usati all'interno di contenitori come i vettori, le mappe eccetera.

Per esempio il codice seguente, a seconda del compilatore usato, potrebbe compilare o no, ma comunque il suo esito e' decisamente incerto

// esempio da NON USARE!
void sortVector() {

   vector<auto_ptr<MyClass>> vettore;
   sort( vettore.begin(), vettore.end() );

}


Come possiamo venirne fuori?

Ci sono altre implementazioni di smart pointers... ma questa e' un'altra storia...

Chiunque può lasciare commenti su questo blog, ammesso che vengano rispettate due regole fondamentali: la buona educazione e il rispetto per gli altri.

Per commentare potete utilizzare diversi modi di autenticazione, da Google a Facebook e Twitter se non volete farvi un account su Disqus che resta sempre la nostra scelta consigliata.

Potete utilizzare tag HTML <b>, <i> e <a> per mettere in grassetto, in corsivo il testo ed inserire link ipertestuali come spiegato in questo tutorial. Per aggiungere un'immagine potete trascinarla dal vostro pc sopra lo spazio commenti.

A questo indirizzo trovate indicazioni su come ricevere notifiche via email sui nuovi commenti pubblicati.

1 commento:

  1. Mi rendo conto solo adesso che purtroppo alcune parti del testo sono scomparse a cause della troppa intelligenza dell'editor.

    Mi scuso, per l'eventuale confusione e vi assicuro che abbiamo gia' preso contatti con Blogger per risolvere questo problema.

    RispondiElimina

Chiunque può lasciare commenti su questo blog, ammesso che vengano rispettate due regole fondamentali: la buona educazione e il rispetto per gli altri.

Per commentare potete utilizzare diversi modi di autenticazione, da Google a Facebook e Twitter se non volete farvi un account su Disqus che resta sempre la nostra scelta consigliata.

Potete utilizzare tag HTML <b>, <i> e <a> per mettere in grassetto, in corsivo il testo ed inserire link ipertestuali come spiegato in questo tutorial. Per aggiungere un'immagine potete trascinarla dal vostro pc sopra lo spazio commenti.

A questo indirizzo trovate indicazioni su come ricevere notifiche via email sui nuovi commenti pubblicati.

Related Posts Plugin for WordPress, Blogger...