Namespaces
Variants

Lifetime

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

Chaque objet et référence possède une durée de vie , qui est une propriété d'exécution : pour tout objet ou référence, il existe un point d'exécution du programme où sa durée de vie commence, et un moment où elle se termine.

La durée de vie d'un objet commence lorsque :

  • si l'objet est un membre d'union ou un sous-objet de celui-ci, sa durée de vie ne commence que si ce membre d'union est le membre initialisé dans l'union, ou s'il est rendu actif,
  • si l'objet est imbriqué dans un objet union, sa durée de vie peut commencer si l'objet union le contenant est assigné ou construit par une fonction membre spéciale triviale,
  • la durée de vie d'un objet tableau peut également commencer s'il est alloué par std::allocator::allocate .

Certaines opérations créent implicitement des objets de types à durée de vie implicite dans une région de stockage donnée et démarrent leur durée de vie. Si un sous-objet d'un objet créé implicitement n'est pas d'un type à durée de vie implicite, sa durée de vie ne commence pas implicitement.

La durée de vie d'un objet se termine lorsque :

  • s'il s'agit d'un type non-classe, l'objet est détruit (éventuellement via un appel de pseudo-destructeur), ou
  • s'il s'agit d'un type classe, l'appel du destructor commence, ou
  • le stockage que l'objet occupe est libéré, ou est réutilisé par un objet qui n'est pas imbriqué à l'intérieur.

La durée de vie d'un objet est égale ou imbriquée dans la durée de vie de son stockage, voir storage duration .

La durée de vie d'une référence commence lorsque son initialisation est terminée et se termine comme s'il s'agissait d'un objet scalaire.

Note : la durée de vie de l'objet référencé peut se terminer avant la fin de la durée de vie de la référence, ce qui rend possible les dangling references .

Les durées de vie des membres de données non statiques et des sous-objets de base commencent et se terminent en suivant l'ordre d'initialisation de la classe .

Table des matières

Durée de vie des objets temporaires

Les objets temporaires sont créés lorsqu'un prvalue est matérialisé afin de pouvoir être utilisé comme glvalue, ce qui se produit (depuis C++17) dans les situations suivantes :

(depuis C++11)
  • retour d'une prvalue depuis une fonction
  • conversion qui crée une prvalue ( incluant T ( a, b, c ) et T { } )
(depuis C++11)
(jusqu'à C++17)
  • lors de l'accès à un membre sur une prvalue de classe
  • lors d'une conversion tableau-vers-pointeur ou d'un indiçage sur une prvalue de tableau
  • pour les opérandes non évalués dans sizeof et typeid
  • lorsqu'une prvalue apparaît comme expression à valeur ignorée

La matérialisation d'un objet temporaire est généralement retardée autant que possible pour éviter de créer des objets temporaires inutiles : voir l'élision de copie .

(depuis C++17)


Lorsqu'un objet de type T est passé à ou retourné par un appel de fonction potentiellement évalué , si T est l'un des types suivants, les implémentations sont autorisées à créer des objets temporaires pour contenir le paramètre de fonction ou l'objet résultat :

(depuis C++26)

L'objet temporaire est construit à partir de l'argument de fonction ou de la valeur de retour, respectivement, et le paramètre de fonction ou l'objet de retour est initialisé comme si on utilisait le constructeur trivial éligible pour copier l'objet temporaire (même si ce constructeur est inaccessible ou ne serait pas sélectionné par la résolution de surcharge pour effectuer une copie ou un déplacement de l'objet).

(jusqu'à C++26)

Les objets temporaires sont créés comme suit :

  • Le premier objet temporaire est construit à partir de l'argument de fonction ou de la valeur de retour, respectivement.
  • Chaque objet temporaire successif est initialisé à partir du précédent comme par initialisation directe si T est un type scalaire, sinon en utilisant un constructeur trivial éligible.
  • Le paramètre de fonction ou l'objet de retour est initialisé à partir du temporaire final comme par initialisation directe si T est un type scalaire, sinon en utilisant un constructeur trivial éligible.

Dans tous les cas, le constructeur éligible est utilisé même si ce constructeur est inaccessible ou ne serait pas sélectionné par la résolution de surcharge pour effectuer une copie ou un déplacement de l'objet.

(depuis C++26)

Cette latitude est accordée pour permettre aux objets d'être passés à ou retournés par des fonctions dans des registres.

(depuis C++17)

Tous les objets temporaires sont détruits comme dernière étape de l'évaluation de la expression complète qui contient (lexicalement) le point où ils ont été créés, et si plusieurs objets temporaires ont été créés, ils sont détruits dans l'ordre inverse de leur création. Ceci reste vrai même si cette évaluation se termine par le lancement d'une exception.

Il existe les exceptions suivantes à cela :

  • La durée de vie d'un objet temporaire peut être prolongée en le liant à une référence, voir l'initialisation des références pour plus de détails.
  • La durée de vie d'un objet temporaire créé lors de l'évaluation des arguments par défaut d'un constructeur par défaut ou de copie utilisé pour initialiser ou copier un élément d'un tableau se termine avant que l'élément suivant du tableau ne commence son initialisation.
  • La durée de vie d'un objet temporaire créé dans une déclaration de liaison structurée (introduite par l'initialiseur pour une variable avec un nom unique) est étendue jusqu'à la fin de la déclaration de liaison structurée.
(depuis C++17)
  • La durée de vie d'un objet temporaire créé dans l'initialiseur de plage d'une instruction range- for qui serait autrement détruit à la fin de l'initialiseur de plage est étendue jusqu'à la fin du corps de la boucle.
(depuis C++23)

Réutilisation du stockage

Un programme n'est pas obligé d'appeler le destructeur d'un objet pour mettre fin à sa durée de vie si l'objet est trivialement destructible (attention que le comportement correct du programme peut dépendre du destructeur). Cependant, si un programme met fin explicitement à la durée de vie d'un objet non trivialement destructible qui est une variable, il doit s'assurer qu'un nouvel objet du même type est construit in-situ (par exemple via new de placement) avant que le destructeur ne puisse être appelé implicitement, c'est-à-dire en raison de la sortie de portée ou d'une exception pour les objets automatiques , en raison de la fin de thread pour les objets thread-locaux, (depuis C++11) ou en raison de la fin du programme pour les objets statiques ; sinon le comportement est indéfini.

class T {}; // trivial
struct B
{
    ~B() {} // non-trivial
};
void x()
{
    long long n; // automatique, trivial
    new (&n) double(3.14); // réutilisation avec un type différent acceptable
} // correct
void h()
{
    B b; // automatique non trivialement destructible
    b.~B(); // fin de durée de vie (non requis, car pas d'effets de bord)
    new (&b) T; // mauvais type : acceptable jusqu'à l'appel du destructeur
} // destructeur appelé : comportement indéfini

Il est un comportement indéfini de réutiliser un stockage qui est ou a été occupé par un objet complet const de durée de stockage statique , thread-local, (depuis C++11) ou automatique car de tels objets peuvent être stockés en mémoire morte :

struct B
{
    B(); // non-trivial
    ~B(); // non-trivial
};
const B b; // const static
void h()
{
    b.~B(); // terminer la durée de vie de b
    new (const_cast<B*>(&b)) const B; // comportement indéfini : tentative de réutilisation d'un const
}

Lors de l'évaluation d'une new expression , la mémoire est considérée comme réutilisée après son retour par la fonction d'allocation , mais avant l'évaluation de l' initialiseur de l'expression new :

struct S
{
    int m;
};
void f()
{
    S x{1};
    new(&x) S(x.m); // comportement indéfini : le stockage est réutilisé
}

Si un nouvel objet est créé à l'adresse qui était occupée par un autre objet, alors tous les pointeurs, références et le nom de l'objet original se référeront automatiquement au nouvel objet et, une fois que la durée de vie du nouvel objet commence, pourront être utilisés pour manipuler le nouvel objet, mais seulement si l'objet original est remplaçable de manière transparente par le nouvel objet.

Si toutes les conditions suivantes sont satisfaites, l'objet x est remplaçable de manière transparente par l'objet y :

  • Le stockage pour y recouvre exactement l'emplacement de stockage qu'occupait x .
  • y est du même type que x (en ignorant les qualificatifs cv de niveau supérieur).
  • x n'est pas un objet const complet.
  • Ni x ni y n'est un sous-objet de classe de base , ou un sous-objet membre déclaré avec [[ no_unique_address ]] (depuis C++20) .
  • Une des conditions suivantes est satisfaite :
  • x et y sont tous deux des objets complets.
  • x et y sont des sous-objets directs des objets ox et oy respectivement, et ox est remplaçable de manière transparente par oy .
struct C
{
    int i;
    void f();
    const C& operator=(const C&);
};
const C& C::operator=(const C& other)
{
    if (this != &other)
    {
        this->~C();          // durée de vie de *this se termine
        new (this) C(other); // nouvel objet de type C créé
        f();                 // bien défini
    }
    return *this;
}
C c1;
C c2;
c1 = c2; // bien défini
c1.f();  // bien défini ; c1 fait référence à un nouvel objet de type C

Si les conditions énumérées ci-dessus ne sont pas remplies, un pointeur valide vers le nouvel objet peut toujours être obtenu en appliquant la barrière d'optimisation de pointeur std::launder :

struct A
{ 
    virtual int transmogrify();
};
struct B : A
{
    int transmogrify() override { ::new(this) A; return 2; }
};
inline int A::transmogrify() { ::new(this) B; return 1; }
void test()
{
    A i;
    int n = i.transmogrify();
    // int m = i.transmogrify(); // comportement indéfini :
    // le nouvel objet A est un sous-objet de base, tandis que l'ancien est un objet complet
    int m = std::launder(&i)->transmogrify(); // OK
    assert(m + n == 3);
}
(depuis C++17)

De même, si un objet est créé dans la mémoire d'un membre de classe ou d'un élément de tableau, l'objet créé n'est qu'un sous-objet (membre ou élément) de l'objet conteneur de l'objet original si :

  • la durée de vie de l'objet conteneur a commencé et ne s'est pas terminée
  • le stockage du nouvel objet recouvre exactement le stockage de l'objet original
  • le nouvel objet est du même type que l'objet original (en ignorant les qualifications cv).

Sinon, le nom du sous-objet original ne peut pas être utilisé pour accéder au nouvel objet sans std::launder :

(depuis C++17)

Fourniture de stockage

En tant que cas particulier, des objets peuvent être créés dans des tableaux de unsigned char ou std::byte (depuis C++17) (auquel cas on dit que le tableau fournit le stockage pour l'objet) si

  • la durée de vie du tableau a commencé et ne s'est pas terminée
  • le stockage pour le nouvel objet tient entièrement dans le tableau
  • il n'y a pas d'objet tableau qui satisfait ces contraintes imbriqué dans le tableau.

Si cette partie du tableau a précédemment fourni le stockage pour un autre objet, la durée de vie de cet objet se termine car son stockage a été réutilisé, cependant la durée de vie du tableau lui-même ne se termine pas (son stockage n'est pas considéré comme ayant été réutilisé).

template<typename... T>
struct AlignedUnion
{
    alignas(T...) unsigned char data[max(sizeof(T)...)];
};
int f()
{
    AlignedUnion<int, char> au;
    int *p = new (au.data) int;     // OK, au.data fournit le stockage
    char *c = new (au.data) char(); // OK, met fin à la durée de vie de *p
    char *d = new (au.data + 1) char();
    return *c + *d; // OK
}

Accès en dehors de la durée de vie

Avant que la durée de vie d'un objet n'ait commencé mais après que le stockage que l'objet occupera a été alloué, ou après que la durée de vie d'un objet a pris fin et avant que le stockage qu'il occupait ne soit réutilisé ou libéré, les comportements des utilisations suivantes de l'expression glvalue qui identifie cet objet sont indéfinis, sauf si l'objet est en cours de construction ou de destruction (un ensemble distinct de règles s'applique) :

  1. Accéder à l'objet.
  2. Accéder à un membre de données non statique ou appeler une fonction membre non statique.
  3. Lier une référence à un sous-objet de classe virtuelle de base.
  4. dynamic_cast ou typeid expressions.

Les règles ci-dessus s'appliquent également aux pointeurs (la liaison d'une référence à une base virtuelle est remplacée par la conversion implicite en un pointeur vers une base virtuelle), avec deux règles supplémentaires :

  1. static_cast d'un pointeur vers un espace mémoire sans objet n'est autorisé que lors d'une conversion vers (éventuellement qualifié cv) void * .
  2. Les pointeurs vers un espace mémoire sans objet qui ont été convertis en void * éventuellement qualifié cv ne peuvent être static_cast qu'en pointeurs vers char éventuellement qualifié cv, ou vers unsigned char éventuellement qualifié cv , ou vers std::byte éventuellement qualifié cv (depuis C++17) .

Pendant la construction et la destruction, il est généralement permis d'appeler des fonctions membres non statiques, d'accéder aux membres de données non statiques, et d'utiliser typeid et dynamic_cast . Cependant, comme la durée de vie n'a pas encore commencé (pendant la construction) ou a déjà pris fin (pendant la destruction), seules des opérations spécifiques sont autorisées. Pour une restriction, voir les appels de fonctions virtuelles pendant la construction et la destruction .

Notes

Jusqu'à la résolution de CWG issue 2256 , les règles de fin de durée de vie diffèrent entre les objets non-classe (fin de la durée de stockage) et les objets classe (ordre inverse de construction) :

struct A
{
    int* p;
    ~A() { std::cout << *p; } // comportement indéfini depuis CWG2256 : n ne survit pas à a
                              // bien défini jusqu'à CWG2256 : affiche 123
};
void f()
{
    A a;
    int n = 123; // si n ne survit pas à a, cela aurait pu être optimisé (stockage mort)
    a.p = &n;
}

Jusqu'à la résolution de RU007 , un membre non statique d'un type qualifié const ou d'un type référence empêche son objet conteneur d'être remplaçable de manière transparente, ce qui rend std::vector et std::deque difficiles à implémenter :

struct X { const int n; };
union U { X x; float f; };
void tong()
{
    U u = { {1} };
    u.f = 5.f;                          // OK : crée un nouveau sous-objet de 'u'
    X *p = new (&u.x) X {2};            // OK : crée un nouveau sous-objet de 'u'
    assert(p->n == 2);                  // OK
    assert(u.x.n == 2);                 // indéfini jusqu'à RU007 :
                                        // 'u.x' ne nomme pas le nouveau sous-objet
    assert(*std::launder(&u.x.n) == 2); // OK même jusqu'à RU007
}

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 119 C++98 un objet d'un type de classe avec un constructeur non trivial ne peut
commencer sa durée de vie que lorsque l'appel du constructeur est terminé
la durée de vie commence également
pour d'autres initialisations
CWG 201 C++98 la durée de vie d'un objet temporaire dans un argument par défaut
d'un constructeur par défaut devait se terminer
lorsque l'initialisation du tableau est terminée
la durée de vie se termine avant
l'initialisation de l'élément
suivant (résout également
CWG issue 124 )
CWG 274 C++98 une lvalue désignant un objet hors durée de vie pouvait être
utilisée comme opérande de static_cast seulement si la conversion
était finalement vers char & ou unsigned char & sans cv-qualification
char & cv-qualifié
et unsigned char &
également autorisés
CWG 597 C++98 les comportements suivants étaient indéfinis :
1. un pointeur vers un objet hors durée de vie est implicitement
converti en pointeur vers une classe de base non virtuelle
2. une lvalue référençant un objet hors durée de vie
est liée à une référence vers une classe de base non virtuelle
3. une lvalue référençant un objet hors durée de vie est utilisée
comme opérande d'un static_cast (avec quelques exceptions)
rendus bien définis
CWG 2012 C++98 la durée de vie des références était spécifiée pour correspondre à la durée de stockage,
exigeant que les références externes soient vivantes avant l'exécution de leurs initialiseurs
la durée de vie commence
à l'initialisation
CWG 2107 C++98 la résolution de CWG issue 124 n'était pas appliquée aux constructeurs de copie appliquée
CWG 2256 C++98 la durée de vie des objets trivialement destructibles était incohérente avec les autres objets rendue cohérente
CWG 2470 C++98 plus d'un tableau pouvait fournir du stockage pour le même objet un seul fournit
CWG 2489 C++98 char [ ] ne peut pas fournir de stockage, mais des objets
pouvaient être implicitement créés dans son stockage
les objets ne peuvent pas être
implicitement créés dans
le stockage de char [ ]
CWG 2527 C++98 si un destructeur n'est pas invoqué en raison de la réutilisation du stockage et que le
programme dépend de ses effets secondaires, le comportement était indéfini
le comportement est bien
défini dans ce cas
CWG 2721 C++98 le moment exact de la réutilisation du stockage n'était pas clair pour le placement new clarifié
CWG 2849 C++23 les objets paramètres de fonction étaient considérés comme des objets
temporaires pour l'extension de durée de vie des objets temporaires
de la boucle range- for
non considérés comme
des objets temporaires
CWG 2854 C++98 les objets d'exception étaient des objets temporaires ils ne sont pas
des objets temporaires
CWG 2867 C++17 la durée de vie des objets temporaires créés dans
les déclarations de liaison structurée n'était pas étendue
étendue jusqu'à la fin
de la déclaration
P0137R1 C++98 créer un objet dans un tableau de unsigned char réutilisait son stockage son stockage n'est pas réutilisé
P0593R6 C++98 un appel de pseudo-destructeur n'avait aucun effet il détruit l'objet
P1971R0 C++98 un membre de données non statique d'un type qualifié const ou d'un type référence
empêchait son objet conteneur d'être transparentment remplaçable
restriction supprimée
P2103R0 C++98 la remplaçabilité transparente ne nécessitait pas de conserver la structure originale nécessite

Références

  • Norme C++23 (ISO/CEI 14882:2024) :
  • 6.7.3 Durée de vie des objets [basic.life]
  • 11.9.5 Construction et destruction [class.cdtor]
  • Norme C++20 (ISO/CEI 14882:2020) :
  • 6.7.3 Durée de vie des objets [basic.life]
  • 11.10.4 Construction et destruction [class.cdtor]
  • Norme C++17 (ISO/IEC 14882:2017) :
  • 6.8 Durée de vie des objets [basic.life]
  • 15.7 Construction et destruction [class.cdtor]
  • Norme C++14 (ISO/IEC 14882:2014) :
  • 3 Durée de vie des objets [basic.life]
  • 12.7 Construction et destruction [class.cdtor]
  • Norme C++11 (ISO/CEI 14882:2011) :
  • 3.8 Durée de vie des objets [basic.life]
  • 12.7 Construction et destruction [class.cdtor]
  • Norme C++03 (ISO/CEI 14882:2003) :
  • 3.8 Durée de vie des objets [basic.life]
  • 12.7 Construction et destruction [class.cdtor]
  • Norme C++98 (ISO/CEI 14882:1998) :
  • 3.8 Durée de vie des objets [basic.life]
  • 12.7 Construction et destruction [class.cdtor]

Voir aussi

Documentation C pour Lifetime