Namespaces
Variants

Destructors

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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)

Table des matières

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 -

spécification d'exception dynamique

(jusqu'à C++11)

soit spécification d'exception dynamique
soit spécification noexcept

(depuis C++11)
(jusqu'à C++17)

spécification noexcept

(depuis C++17)
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 :

  • 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

  • fin de thread, pour les objets avec une durée de stockage locale au thread
(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 potentiel

Une 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 T , le compilateur déclarera toujours implicitement un, et le destructeur potentiel implicitement déclaré est également le destructeur pour T .

#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 :

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 T est défini comme supprimé si l'une des conditions suivantes est satisfaite :

  • est supprimé ou inaccessible depuis le destructeur de T , ou
  • dans le cas où le sous-objet est un membre variant , est non trivial.
(jusqu'en C++26)
  • T n'est pas une union, et a un sous-objet potentiellement construit non- variant de type classe M (ou éventuellement un tableau multidimensionnel de celui-ci) tel que M a un destructeur qui est supprimé ou inaccessible depuis le destructeur de T .
  • T est une union, et l'une des conditions suivantes est satisfaite :
  • La résolution de surcharge pour sélectionner un constructeur pour initialiser par défaut un objet de type T échoue ou sélectionne un constructeur qui est soit supprimé soit non trivial.
  • T a un membre variant V de type classe M (ou éventuellement un tableau multidimensionnel de celui-ci) où V a un initialiseur par défaut et M a un destructeur qui est non trivial.
(depuis C++26)
  • une ambiguïté, ou
  • une fonction qui est supprimée ou inaccessible depuis le destructeur.

Un destructeur prospectif explicitement par défaut pour T est défini comme supprimé s'il n'est pas le destructeur pour T .

(depuis C++20)
(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.
  • Chaque membre de données non statique de type classe (ou tableau de type classe) a un destructeur trivial.
(jusqu'à C++26)
  • Soit T est une union, soit chaque membre de données non statique non variant de type classe (ou tableau de type classe) a un destructeur trivial.
(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

Voir aussi