Destructors
Un destructeur est une fonction membre spéciale qui est appelée lorsque la durée de vie d'un objet se termine. Le but du destructeur est de libérer les ressources que l'objet a pu acquérir durant sa durée de vie.
|
Un destructeur ne peut pas être une coroutine . |
(depuis C++20) |
Syntaxe
Destructeurs (jusqu'au C++20) Destructeurs prospectifs (depuis C++20) sont déclarés en utilisant des déclarateurs de fonction membre de la forme suivante :
class-name-with-tilde
(
parameter-list
(optionnel)
)
except
(optionnel)
attr
(optionnel)
|
|||||||||
| class-name-with-tilde | - | une expression d'identifiant , éventuellement suivie d'une liste d'attributs , et (depuis C++11) éventuellement entourée d'une paire de parenthèses | ||||||
| parameter-list | - | liste de paramètres (doit être soit vide soit void ) | ||||||
| except | - |
|
||||||
| attr | - | (depuis C++11) une liste d'attributs |
Les seuls spécificateurs autorisés dans les
spécificateurs de déclaration
d'une déclaration de destructeur
prospectif
(depuis C++20)
sont
constexpr
,
(depuis C++11)
friend
,
inline
et
virtual
(en particulier, aucun type de retour n'est autorisé).
L'expression d'identificateur de class-name-with-tilde doit avoir l'une des formes suivantes :
- Dans une déclaration de membre qui appartient à la spécification de membre d'une classe ou d'un modèle de classe, mais qui n'est pas une déclaration friend :
-
- Pour les classes, l'expression d'identifiant est ~ suivi du injected-class-name de la classe englobante immédiate.
- Pour les modèles de classe, l'expression d'identifiant est ~ suivi de un nom de classe qui désigne l' current instantiation (jusqu'à C++20) l'injected-class-name (depuis C++20) du modèle de classe englobant immédiat.
- Sinon, l'expression d'identifiant est un identifiant qualifié dont l'identifiant non qualifié terminal est ~ suivi du nom de classe injecté de la classe désignée par les parties non terminales de l'identifiant qualifié.
Explication
Le destructeur est implicitement invoqué chaque fois que la durée de vie d'un objet se termine, ce qui inclut
- terminaison du programme , pour les objets avec une durée de stockage statique
|
(depuis C++11) |
- fin de portée, pour les objets avec durée de stockage automatique et pour les temporaires dont la durée de vie a été prolongée par liaison à une référence
- delete expression , pour les objets avec durée de stockage dynamique
- fin de l' expression complète, pour les temporaires sans nom
- stack unwinding , pour les objets avec durée de stockage automatique lorsqu'une exception échappe à leur bloc, non capturée.
Le destructeur peut également être invoqué explicitement.
Destructeur potentielUne classe peut avoir un ou plusieurs destructeurs potentiels, dont l'un est sélectionné comme destructeur de la classe. Afin de déterminer quel destructeur potentiel est le destructeur, à la fin de la définition de la classe, la résolution de surcharge est effectuée parmi les destructeurs potentiels déclarés dans la classe avec une liste d'arguments vide. Si la résolution de surcharge échoue, le programme est mal formé. La sélection du destructeur n'utilise pas odr-use le destructeur sélectionné, et le destructeur sélectionné peut être supprimé.
Tous les destructeurs potentiels sont des fonctions membres spéciales. Si aucun destructeur potentiel déclaré par l'utilisateur n'est fourni pour la classe
Exécuter ce code
#include <cstdio> #include <type_traits> template<typename T> struct A { ~A() requires std::is_integral_v<T> { std::puts("~A, T is integral"); } ~A() requires std::is_pointer_v<T> { std::puts("~A, T is a pointer"); } ~A() { std::puts("~A, T is anything else"); } }; int main() { A<int> a; A<int*> b; A<float> c; } Sortie : ~A, T is anything else ~A, T is a pointer ~A, T is integral |
(depuis C++20) |
Destructeur potentiellement invoqué
Le destructeur pour la classe
T
est
potentiellement invoqué
dans les situations suivantes :
- Il est invoqué explicitement ou implicitement.
-
Une
new
expression
crée un tableau d'objets de type
T. -
L'objet résultant d'une
return
statement
est de type
T. -
Un tableau est soumis à une
aggregate initialization
, et son type d'élément est
T. -
Un objet de classe est soumis à une initialisation agrégée, et il possède un membre de type
ToùTn'est pas un type anonymous union . -
Un
potentially constructed subobject
est de type
Tdans un constructeur non- delegating (since C++11) . -
Un
exception object
de type
Test construit.
Si un destructeur potentiellement invoqué est supprimé ou (depuis C++11) non accessible depuis le contexte de l'invocation, le programme est mal formé.
Destructeur implicitement déclaré
Si aucun destructeur prospectif (depuis C++20) déclaré par l'utilisateur n'est fourni pour un type classe , le compilateur déclarera toujours un destructeur comme membre inline public de sa classe.
Comme pour toute fonction membre spéciale implicitement déclarée, la spécification d'exception du destructeur implicitement déclaré est non levante sauf si le destructeur de toute base ou membre potentiellement construit est potentiellement levante (depuis C++17) la définition implicite invoquerait directement une fonction avec une spécification d'exception différente (jusqu'à C++17) . En pratique, les destructeurs implicites sont noexcept sauf si la classe est "empoisonnée" par une base ou un membre dont le destructeur est noexcept ( false ) .
Destructeur implicitement défini
Si un destructeur implicitement déclaré n'est pas supprimé, il est implicitement défini (c'est-à-dire qu'un corps de fonction est généré et compilé) par le compilateur lorsqu'il est odr-used . Ce destructeur implicitement défini a un corps vide.
|
Si cela satisfait aux exigences d'un destructeur constexpr (jusqu'en C++23) fonction constexpr (depuis C++23) , le destructeur généré est constexpr . |
(depuis C++20) |
Destructeur supprimé
Le destructeur implicitement déclaré ou explicitement par défaut pour la classe
|
(depuis C++11) |
Destructeur trivial
Le destructeur de la classe
T
est trivial si toutes les conditions suivantes sont satisfaites :
- Le destructeur est implicitement déclaré (jusqu'en C++11) non fourni par l'utilisateur (depuis C++11) .
- Le destructeur n'est pas virtuel.
- Toutes les classes de base directes ont des destructeurs triviaux.
|
(jusqu'à C++26) |
|
(depuis C++26) |
Un destructeur trivial est un destructeur qui n'effectue aucune action. Les objets avec des destructeurs triviaux ne nécessitent pas d'expression delete et peuvent être éliminés en libérant simplement leur stockage. Tous les types de données compatibles avec le langage C (types POD) sont trivialement destructibles.
Séquence de destruction
Pour les destructeurs définis par l'utilisateur ou implicitement définis, après l'exécution du corps du destructeur et la destruction de tout objet automatique alloué dans le corps, le compilateur appelle les destructeurs pour tous les membres de données non statiques et non variants de la classe, dans l'ordre inverse de leur déclaration, puis il appelle les destructeurs de toutes les classes de base directes non virtuelles dans l'ordre inverse de construction (qui à leur tour appellent les destructeurs de leurs membres et de leurs classes de base, etc.), et ensuite, si cet objet est de classe la plus dérivée , il appelle les destructeurs de toutes les bases virtuelles.
Même lorsque le destructeur est appelé directement (par ex. obj.~Foo ( ) ; ), l'instruction return dans ~Foo ( ) ne retourne pas le contrôle à l'appelant immédiatement : il appelle d'abord tous ces destructeurs de membres et de bases.
Destructeurs virtuels
Supprimer un objet via un pointeur vers la base invoque un comportement indéfini sauf si le destructeur dans la classe de base est virtual :
class Base { public: virtual ~Base() {} }; class Derived : public Base {}; Base* b = new Derived; delete b; // sûr
Une directive courante est que le destructeur d'une classe de base doit être soit public et virtuel, soit protégé et non virtuel .
Destructeurs virtuels purs
Un destructeur (depuis C++20) potentiel peut être déclaré virtuel pur , par exemple dans une classe de base qui doit être rendue abstraite, mais qui n'a pas d'autres fonctions appropriées pouvant être déclarées virtuelles pures. Un destructeur virtuel pur doit avoir une définition, car tous les destructeurs des classes de base sont toujours appelés lorsque la classe dérivée est détruite :
class AbstractBase { public: virtual ~AbstractBase() = 0; }; AbstractBase::~AbstractBase() {} class Derived : public AbstractBase {}; // AbstractBase obj; // erreur du compilateur Derived obj; // OK
Exceptions
Comme toute autre fonction, un destructeur peut se terminer en lançant une exception (cela nécessite généralement qu'il soit explicitement déclaré noexcept ( false ) ) (depuis C++11) , cependant si ce destructeur est appelé pendant le déroulement de pile , std::terminate est appelé à la place.
Bien que std::uncaught_exceptions puisse parfois être utilisé pour détecter un déroulement de pile en cours, il est généralement considéré comme une mauvaise pratique de permettre à un destructeur de se terminer en lançant une exception. Cette fonctionnalité est néanmoins utilisée par certaines bibliothèques, telles que SOCI et Galera 3 , qui s'appuient sur la capacité des destructeurs des temporaires sans nom à lancer des exceptions à la fin de l'expression complète qui construit le temporaire.
std::experimental::scope_success dans la Library fundamental TS v3 peut avoir un destructeur potentiellement lanceur , qui lance une exception lorsque la portée est quittée normalement et que la fonction de sortie lance une exception.
Notes
Appeler un destructeur directement pour un objet ordinaire, tel qu'une variable locale, invoque un comportement indéfini lorsque le destructeur est appelé à nouveau, à la fin de la portée.
Dans les contextes génériques, la syntaxe d'appel du destructeur peut être utilisée avec un objet de type non-classe ; ceci est appelé appel pseudo-destructeur : voir opérateur d'accès membre .
| Macro de test de fonctionnalité | Valeur | Std | Fonctionnalité |
|---|---|---|---|
__cpp_trivial_union
|
202502L
|
(C++26) | Assouplissement des exigences de trivialité pour les fonctions membres spéciales des unions |
Exemple
#include <iostream> struct A { int i; A(int num) : i(num) { std::cout << "ctor a" << i << '\n'; } (~A)() // mais généralement ~A() { std::cout << "dtor a" << i << '\n'; } }; A a0(0); int main() { A a1(1); A* p; { // portée imbriquée A a2(2); p = new A(3); } // a2 hors de portée delete p; // appelle le destructeur de a3 }
Sortie :
ctor a0 ctor a1 ctor a2 ctor a3 dtor a2 dtor a3 dtor a1 dtor a0
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 193 | C++98 |
le moment de la destruction des objets automatiques dans un destructeur
(avant ou après la destruction des sous-objets de base et membres de la classe) n'était pas spécifié |
ils sont détruits
avant la destruction de ces sous-objets |
| CWG 344 | C++98 |
la syntaxe du déclarateur de destructeur était défectueuse (avait le
même problème que CWG issue 194 et CWG issue 263 |
modifié la syntaxe vers une syntaxe
de déclarateur de fonction spécialisée |
| CWG 1241 | C++98 |
les membres statiques pouvaient être détruits
immédiatement après l'exécution du destructeur |
ne détruit que les membres
non-statiques |
| CWG 1353 | C++98 |
les conditions où les destructeurs implicitement déclarés sont
indéfinis ne prenaient pas en compte les types de tableaux multidimensionnels |
prend en compte ces types |
| CWG 1435 | C++98 |
la signification de « nom de classe » dans la
syntaxe du déclarateur de destructeur n'était pas claire |
modifié la syntaxe vers une syntaxe
de déclarateur de fonction spécialisée |
| CWG 2180 | C++98 |
le destructeur d'une classe qui n'est pas une classe la plus dérivée
appellerait les destructeurs de ses classes de base virtuelles directes |
il n'appellera pas ces destructeurs |
| CWG 2807 | C++20 | les spécificateurs de déclaration pouvaient contenir consteval | interdit |