HOWTO · C++
Chiama un distruttore esplicitamente in C++
In questo articolo viene illustrato come chiamare un distruttore in modo esplicito in C++
In questa pagina
In C++, scrivi obj.~ClassName() per invocare esplicitamente il distruttore di un oggetto. In questo modo termina la durata di vita dell’oggetto: non usarlo più, a meno che nella stessa area di memoria non venga creato un nuovo oggetto. Le chiamate manuali sono rare; di solito è più sicuro affidarsi alla pulizia automatica alla fine dello scope o a un tipo proprietario come std::unique_ptr.
Usa la notazione obj.~ClassName() per chiamare esplicitamente una funzione distruttore
Un distruttore rilascia le risorse possedute da un oggetto quando termina la sua durata di vita. Il compilatore lo chiama automaticamente quando un oggetto automatico esce dal proprio scope. Per un puntatore restituito da new, delete chiama il distruttore e poi libera la memoria dell’oggetto. Questi normali meccanismi non vanno combinati con un’ulteriore chiamata manuale: distruggere esplicitamente un oggetto automatico e lasciarlo poi uscire dallo scope significa tentare di distruggere un oggetto la cui durata di vita è già terminata. Questo comportamento è indefinito: non garantisce di stampare due messaggi. Allo stesso modo, distruggere manualmente un oggetto creato con new e applicare in seguito delete allo stesso puntatore non è una sequenza di pulizia valida.
Una chiamata esplicita è utile nel codice di basso livello gestito con attenzione, per esempio quando un oggetto occupa memoria fornita dal chiamante e il programma intende costruirvi un altro oggetto. L’esempio seguente usa il placement new per avviare la durata di vita di un oggetto in un buffer di byte, ne invoca il distruttore e poi costruisce un oggetto sostitutivo allo stesso indirizzo. L’ultima chiamata esplicita termina la durata di vita dell’oggetto sostitutivo; il buffer di byte non distrugge automaticamente gli oggetti collocati al suo interno.
La chiamata usa l’espressione dell’oggetto, seguita da .~ e dal nome della classe: per un oggetto chiamato obj di classe Widget, la forma è obj.~Widget(). Il distruttore non restituisce alcun valore e non accetta argomenti. Si applicano comunque le regole di accesso: il codice non può chiamare un distruttore privato se non ha il diritto di accedervi. La chiamata esegue le operazioni di pulizia del distruttore, ma non libera la memoria ottenuta da un allocatore o da new; la gestione della memoria è una responsabilità distinta.
Nell’esempio, alignas(Widget) assegna al buffer di byte l’allineamento richiesto da Widget, e la sua dimensione è sufficiente per un’istanza. Il placement new costruisce l’oggetto nel buffer già esistente invece di richiedere un’allocazione separata. La prima chiamata esplicita termina la durata di vita della prima istanza prima che venga costruito l’oggetto sostitutivo. Anche l’oggetto sostitutivo viene poi distrutto esplicitamente. Poiché la memoria è un array di byte locale, quando termina il suo scope termina soltanto la durata di vita dell’array: non viene chiamato il distruttore di un Widget collocato al suo interno. Con questo schema il programmatore deve tenere traccia dell’oggetto attivo nella memoria e assicurarsi che ogni oggetto costruito venga distrutto una sola volta.
Nel normale codice applicativo, una variabile locale è più semplice: il suo distruttore viene eseguito quando il controllo esce dal blocco, anche quando termina una funzione, con o senza restituzione di un valore. std::unique_ptr<Widget> è adatto quando serve una gestione dinamica della proprietà, perché lega la distruzione dell’oggetto alla durata di vita del proprietario. Se un oggetto fa parte di un contenitore o di un’altra classe, lascia che sia il proprietario a gestirne la distruzione invece di chiamare manualmente il distruttore dell’oggetto membro. Questi approcci rendono evidente la pulizia nella struttura del programma ed evitano di usare un oggetto dopo che la sua durata di vita è terminata.
Esegui questo esempio:
#include <iostream>
#include <new>
#include <string>
#include <utility>
class Widget {
public:
explicit Widget(std::string name) : name_(std::move(name)) {
std::cout << "created " << name_ << '\n';
}
~Widget() { std::cout << "destroyed " << name_ << '\n'; }
private:
std::string name_;
};
int main() {
alignas(Widget) unsigned char storage[sizeof(Widget)];
Widget* obj = ::new (static_cast<void*>(storage)) Widget("first");
obj->~Widget();
obj = ::new (static_cast<void*>(storage)) Widget("second");
obj->~Widget();
}
created first
destroyed first
created second
destroyed second
Nel codice che non gestisce direttamente memoria e durate di vita, lascia che lo scope di un oggetto locale termini normalmente. Per la proprietà di oggetti allocati dinamicamente, preferisci std::unique_ptr: il suo distruttore usa delete quando il proprietario esce dallo scope. Non accedere a un puntatore dopo che l’oggetto a cui punta è stato distrutto e non invocare manualmente un distruttore solo per rilasciare una risorsa membro: usa invece un proprietario della libreria standard o un apposito wrapper per la risorsa.