Dans le texte qui suit, je vais distinguer entre les objets dans l'étendue, dont la durée de destruction est statiquement déterminé par leur cadre englobant (fonctions, blocs, des classes, des expressions), et les objets dynamiques, dont l'heure exacte de la destruction n'est généralement pas connue jusqu'à ce que l'exécution.
Tandis que la destruction de la sémantique des objets de classe sont déterminées par les destructeurs, la destruction d'un scalaire objet est toujours un no-op. Plus précisément, la destruction d'un pointeur de variable ne pas détruire la pointee.
Les objets dans l'étendue
automatique des objets
Automatique des objets (communément appelées "variables locales") sont détruits, dans l'ordre inverse de leur définition, lorsque le contrôle de flux laisse la portée de leur définition:
void some_function()
{
Foo a;
Foo b;
if (some_condition)
{
Foo y;
Foo z;
} <--- z and y are destructed here
} <--- b and a are destructed here
Si une exception est levée lors de l'exécution d'une fonction, tous construits précédemment automatique des objets sont détruits avant que l'exception est propagée à l'appelant. Ce processus est appelé le déroulement de pile. Pendant le déroulement de pile, pas d'autres exceptions peuvent laisser les destructeurs de ladite déjà construits automatique des objets. Sinon, la fonction std::terminate
est appelé.
Cela mène à l'une des directives les plus importantes en C++:
Les destructeurs ne devrait jamais jeter.
non-local objets statiques
Les objets statiques définies à l'espace de noms de la portée (communément appelées "variables globales") et les données membres statiques sont détruits, dans l'ordre inverse de leur définition, après l'exécution de l' main
:
struct X
{
static Foo x; // this is only a *declaration*, not a *definition*
};
Foo a;
Foo b;
int main()
{
} <--- y, x, b and a are destructed here
Foo X::x; // this is the respective definition
Foo y;
Notez que l'ordre relatif de la construction (et la destruction) de la statique des objets définis dans les différentes unités de traduction n'est pas défini.
Si une exception quitte le destructeur d'un objet statique, la fonction std::terminate
est appelé.
local objets statiques
Les objets statiques définies à l'intérieur des fonctions sont construites lorsque (et si) le contrôle de flux qui passe par leur définition pour la première fois.1
Ils sont détruits dans l'ordre inverse, après l'exécution d' main
:
Foo& get_some_Foo()
{
static Foo x;
return x;
}
Bar& get_some_Bar()
{
static Bar y;
return y;
}
int main()
{
get_some_Bar().do_something(); // note that get_some_Bar is called *first*
get_some_Foo().do_something();
} <--- x and y are destructed here // hence y is destructed *last*
Si une exception quitte le destructeur d'un objet statique, la fonction std::terminate
est appelé.
1: il s'agit d'un modèle simplifié. Les détails d'initialisation des objets statiques sont en réalité beaucoup plus compliqué.
classe de base des sous-objets et membre de la sous-objets
Lorsque le contrôle de flux de feuilles le destructeur d'un objet, son membre sous-objets (également connu sous le nom de "membres de données") sont détruits dans l'ordre inverse de leur définition. Après cela, sa classe de base sous-objets sont détruits dans l'ordre inverse de la base spécificateur de liste:
class Foo : Bar, Baz
{
Quux x;
Quux y;
public:
~Foo()
{
} <--- y and x are destructed here,
}; followed by the Baz and Bar base class subobjects
Si une exception est levée lors de la construction de l'une des Foo
's sous-objets, alors toutes ses déjà construit les sous-objets seront détruits avant que l'exception est propagée. L' Foo
destructeur, d'autre part, de ne pas être exécuté, depuis l' Foo
objet n'a jamais été entièrement construite.
Notez que le destructeur corps n'est pas responsable de la destruction des données membres eux-mêmes. Il vous suffit d'écrire un destructeur si un membre de données est un handle vers une ressource qui doit être libérée lorsque l'objet est détruit (tel qu'un fichier, une douille, une connexion de base de données, un mutex, ou d'un segment de mémoire).
les éléments du tableau
Les éléments du tableau sont détruits dans l'ordre décroissant. Si une exception est levée lors de la construction de la n-ième élément, les éléments de n-1 à 0 sont détruits avant que l'exception est propagée.
les objets temporaires
Un objet temporaire est construit quand un prvalue expression de type classe est évaluée. L'exemple le plus frappant d'un prvalue expression est l'appel d'une fonction qui retourne un objet par valeur, comme T operator+(const T&, const T&)
. Dans des circonstances normales, l'objet temporaire est détruite lors de la pleine expression que sur le plan lexical contient les prvalue est complètement évaluée:
__________________________ full-expression
___________ subexpression
_______ subexpression
some_function(a + " " + b);
^ both temporary objects are destructed here
La fonction ci-dessus appellent some_function(a + " " + b)
est une expression qui ne fait pas partie d'une expression plus grande (au lieu de cela, il est une partie d'une expression-déclaration). Par conséquent, tous les objets temporaires qui sont construits lors de l'évaluation des sous-expressions seront détruits au point-virgule. Il y a deux tels objets temporaires: le premier est construit au cours de la première addition, et le second est construit pendant la seconde plus. Le deuxième objet temporaire sera détruite avant la première.
Si une exception est levée lors de la deuxième plus, le premier objet temporaire seront détruits correctement avant de propagation de l'exception.
Si une référence locale est initialisé avec un prvalue expression, la durée de vie de l'objet temporaire est étendue à la portée de la référence locale, de sorte que vous ne sera pas obtenir un bancales de référence:
{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
// ...
} <--- second temporary (a + " " + b) is destructed not until here
Si un prvalue expression de la non-type de classe est évaluée, le résultat est une valeur, pas un objet temporaire. Cependant, un objet temporaire va être construit si la prvalue est utilisé pour initialiser une référence:
const int& r = i + j;
Dynamique des objets et des tableaux
Dans la section suivante, détruisez X signifie "d'abord la destruction des X, puis relâchez-la mémoire sous-jacente".
De même, la création de X signifie "premier allouer suffisamment de mémoire et ensuite construire des X-y".
les objets dynamiques
Un objet dynamique créée par p = new Foo
est détruit par delete p
. Si vous oubliez d' delete p
, vous avez une fuite de ressources. Vous ne devriez jamais tenter d'effectuer l'une des opérations suivantes, car elles conduisent toutes à un comportement indéfini:
- détruire un objet dynamique via
delete[]
(note entre crochets), free
ou tout autre moyen
- détruire un objet dynamique à plusieurs reprises
- accéder à un objet dynamique après qu'il a été détruit
Si une exception est levée lors de la construction d'un objet dynamique, la mémoire sous-jacente est libéré avant que l'exception est propagée.
(Le destructeur sera pas exécuté avant pour libérer de la mémoire, parce que l'objet n'a jamais été entièrement construite.)
les tableaux dynamiques
Un tableau dynamique créée par p = new Foo[n]
est détruit par delete[] p
(note entre crochets). Si vous oubliez d' delete[] p
, vous avez une fuite de ressources. Vous ne devriez jamais tenter d'effectuer l'une des opérations suivantes, car elles conduisent toutes à un comportement indéfini:
- détruire un tableau dynamique via
delete
, free
ou tout autre moyen
- détruire un tableau dynamique à plusieurs reprises
- accéder à un tableau dynamique après qu'il a été détruit
Si une exception est levée lors de la construction de la n-ième élément, les éléments de n-1 à 0 sont détruits dans l'ordre décroissant, la mémoire sous-jacente est libéré, et l'exception est propagée.
(Vous devriez généralement préfèrent std::vector<Foo>
sur Foo*
pour les tableaux dynamiques. Il offre une écriture correcte et robuste code beaucoup plus facile.)
comptage de références pointeurs intelligents
Un objet dynamique géré par plusieurs std::shared_ptr<Foo>
des objets sont détruits lors de la destruction de la dernière std::shared_ptr<Foo>
d'objets impliqués dans le partage qu'objet dynamique.
(Vous devriez généralement préfèrent std::shared_ptr<Foo>
sur Foo*
pour les objets partagés. Il offre une écriture correcte et robuste code beaucoup plus facile.)