HOWTO · C++
Appeler un destructeur explicitement en C++
Cet article montre comment appeler un destructeur explicitement en C++
Sur cette page
En C++, écrivez obj.~ClassName() pour appeler explicitement le destructeur d’un objet. Cet appel met fin à la durée de vie de l’objet : ne l’utilisez plus, sauf si un nouvel objet est créé dans son emplacement mémoire. Les appels manuels sont rares ; le nettoyage automatique à la sortie de portée ou un type propriétaire comme std::unique_ptr est généralement plus sûr.
Utiliser la notation obj.~ClassName() pour appeler explicitement une fonction destructrice
Un destructeur libère les ressources détenues par un objet lorsque sa durée de vie prend fin. Le compilateur l’appelle automatiquement lorsqu’un objet à durée de stockage automatique sort de sa portée. Pour un pointeur renvoyé par new, delete appelle le destructeur, puis libère l’espace mémoire de l’objet. Il ne faut pas combiner ces mécanismes normaux avec un appel manuel supplémentaire : détruire explicitement un objet automatique, puis le laisser sortir de sa portée, revient à détruire un objet dont la durée de vie a déjà pris fin. Le comportement est indéfini ; rien ne garantit l’affichage de deux messages. De même, détruire manuellement un objet créé avec new, puis appliquer delete au même pointeur ne constitue pas une séquence de nettoyage valide.
Un appel explicite peut être utile dans du code bas niveau soigneusement maîtrisé, par exemple lorsqu’un objet occupe un espace mémoire fourni par l’appelant et que le programme doit y construire un autre objet. L’exemple suivant utilise le placement new pour commencer la durée de vie d’un objet dans un tampon d’octets, appelle son destructeur, puis construit un objet de remplacement à la même adresse. Le dernier appel explicite met fin à la durée de vie de l’objet de remplacement ; le tampon d’octets ne détruit pas automatiquement les objets qu’il contient.
L’appel s’écrit avec l’expression de l’objet, suivie de .~ et du nom de sa classe : pour un objet nommé obj de la classe Widget, la forme est obj.~Widget(). Le destructeur ne renvoie aucune valeur et ne prend aucun argument. Les règles d’accès s’appliquent aussi : un code qui n’y a pas accès ne peut pas appeler un destructeur privé. L’appel exécute le nettoyage du destructeur, mais ne libère pas l’espace mémoire obtenu auprès d’un allocateur ou avec new ; la gestion de cet espace est une responsabilité distincte.
Dans l’exemple, alignas(Widget) donne au tampon d’octets l’alignement requis pour un Widget, et sa taille suffit pour une instance. Le placement new construit l’objet dans ce tampon existant au lieu de demander une allocation distincte. Le premier appel explicite met fin à la durée de vie de la première instance avant la construction de l’objet de remplacement. Ce dernier est ensuite détruit explicitement à son tour. Comme l’espace mémoire est un tableau local d’octets, la sortie de sa portée met uniquement fin à la durée de vie du tableau ; elle n’appelle pas le destructeur d’un Widget qui y aurait été placé. Le programmeur doit suivre quel objet est vivant dans cet espace et veiller à détruire exactement une fois chaque objet construit. L’exemple affiche des messages de construction et de destruction pour les deux instances ; les libellés affichés dans le bloc inchangé restent en anglais.
Pour le code d’application ordinaire, une variable locale est plus simple : son destructeur s’exécute lorsque le contrôle quitte le bloc, y compris au retour d’une fonction. std::unique_ptr<Widget> convient lorsque la propriété doit être dynamique, car la destruction de l’objet est liée à la durée de vie de son propriétaire. Si un objet fait partie d’un conteneur ou d’une autre classe, laissez ce propriétaire gérer sa destruction au lieu d’appeler manuellement le destructeur de l’objet membre. Ces approches rendent le nettoyage visible dans la structure du programme et évitent d’utiliser un objet après la fin de sa durée de vie.
Exécutez cet exemple :
#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
Dans le code qui ne gère pas directement l’espace mémoire et les durées de vie, laissez naturellement une variable locale sortir de sa portée. Pour une propriété dynamique, préférez std::unique_ptr : son destructeur utilise delete lorsque le propriétaire sort de sa portée. N’accédez pas à un pointeur après la destruction de son objet et n’appelez pas manuellement un destructeur dans le seul but de libérer une ressource membre ; utilisez plutôt un propriétaire de la bibliothèque standard ou un wrapper de ressource adapté.