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

Jedes Objekt und jede Referenz hat eine Lebensdauer , welche eine Laufzeiteigenschaft ist: Für jedes Objekt und jede Referenz gibt es einen Punkt in der Programmausführung, an dem ihre Lebensdauer beginnt, und einen Moment, an dem sie endet.

Die Lebensdauer eines Objekts beginnt, wenn:

  • wenn das Objekt ein union member oder ein Unterobjekt davon ist, beginnt seine Lebensdauer nur, wenn dieses Union-Mitglied das initialisierte Mitglied in der Union ist oder aktiviert wird,
  • wenn das Objekt in einem Union-Objekt verschachtelt ist, kann seine Lebensdauer beginnen, wenn das enthaltende Union-Objekt durch eine triviale Spezial-Memberfunktion zugewiesen oder konstruiert wird,
  • die Lebensdauer eines Array-Objekts kann ebenfalls beginnen, wenn es durch std::allocator::allocate allokiert wird.

Einige Operationen erstellen Objekte implizit von Implicit-Lifetime-Typen in einem gegebenen Speicherbereich und beginnen ihre Lebensdauer. Wenn ein Teilobjekt eines implizit erstellten Objekts nicht von einem Implicit-Lifetime-Typ ist, beginnt seine Lebensdauer nicht implizit.

Die Lebensdauer eines Objekts endet, wenn:

  • wenn es sich um einen Nicht-Klassentyp handelt, wird das Objekt zerstört (möglicherweise über einen Pseudo-Destruktoraufruf), oder
  • wenn es sich um einen Klassentyp handelt, beginnt der Destruktor -Aufruf, oder
  • der Speicher, den das Objekt belegt, wird freigegeben oder wird wiederverwendet durch ein Objekt, das nicht darin verschachtelt ist.

Die Lebensdauer eines Objekts ist gleich oder innerhalb der Lebensdauer seines Speichers geschachtelt, siehe storage duration .

Die Lebensdauer einer Referenz beginnt, wenn ihre Initialisierung abgeschlossen ist, und endet, als ob sie ein skalares Objekt wäre.

Hinweis: Die Lebensdauer des referenzierten Objekts kann vor dem Ende der Lebensdauer der Referenz enden, was dangling references möglich macht.

Die Lebensdauern von nicht-statischen Datenelementen und Basis-Subobjekten beginnen und enden gemäß der Klasseninitialisierungsreihenfolge .

Inhaltsverzeichnis

Lebensdauer temporärer Objekte

Temporäre Objekte werden erstellt, wenn ein prvalue materialisiert wird, damit es als glvalue verwendet werden kann, was (since C++17) in den folgenden Situationen auftritt:

(seit C++11)
(seit C++11)
(bis C++17)

Die Materialisierung eines temporären Objekts wird generell so lange wie möglich verzögert, um die Erstellung unnötiger temporärer Objekte zu vermeiden: siehe Copy Elision .

(seit C++17)


Wenn ein Objekt vom Typ T an einen potenziell ausgewerteten Funktionsaufruf übergeben oder von diesem zurückgegeben wird, dürfen Implementierungen temporäre Objekte zur Aufnahme des Funktionsparameters oder Ergebnisobjekts erstellen, falls T einer der folgenden Typen ist:

(seit C++26)
  • ein Klassentyp, der alle folgenden Bedingungen erfüllt:
    • T besitzt mindestens einen qualifizierten Copy- oder Move-Konstruktor .
    • Jeder qualifizierte Copy-/Move-Konstruktor von T ist trivial.
    • Der Destruktor von T ist entweder trivial oder gelöscht.

Das temporäre Objekt wird jeweils aus dem Funktionsargument oder Rückgabewert konstruiert, und der Funktionsparameter oder das Rückgabeobjekt wird initialisiert, als würde der qualifizierte triviale Konstruktor verwendet, um das temporäre Objekt zu kopieren (selbst wenn dieser Konstruktor unzugänglich ist oder durch Overload Resolution nicht für eine Kopie oder Verschiebung des Objekts ausgewählt werden würde).

(bis C++26)

Die temporären Objekte werden wie folgt erstellt:

  • Das erste solche temporäre Objekt wird jeweils aus dem Funktionsargument oder Rückgabewert konstruiert.
  • Jedes nachfolgende temporäre Objekt wird vom vorherigen initialisiert, als wäre es durch Direktinitialisierung initialisiert worden, falls T ein Skalartyp ist, andernfalls durch Verwendung eines qualifizierten trivialen Konstruktors.
  • Der Funktionsparameter oder das Rückgabeobjekt wird vom letzten temporären Objekt initialisiert, als wäre es durch Direktinitialisierung initialisiert worden, falls T ein Skalartyp ist, andernfalls durch Verwendung eines qualifizierten trivialen Konstruktors.

In allen Fällen wird der qualifizierte Konstruktor verwendet, selbst wenn dieser Konstruktor unzugänglich ist oder durch Overload Resolution nicht für eine Kopie oder Verschiebung des Objekts ausgewählt werden würde.

(seit C++26)

Diese Freiheit wird gewährt, um die Übergabe oder Rückgabe von Objekten an Funktionen in Registern zu ermöglichen.

(seit C++17)

Alle temporären Objekte werden als letzter Schritt bei der Auswertung des full-expression zerstört, der (lexikalisch) den Punkt enthält, an dem sie erstellt wurden, und wenn mehrere temporäre Objekte erstellt wurden, werden sie in der umgekehrten Reihenfolge ihrer Erstellung zerstört. Dies gilt auch dann, wenn diese Auswertung mit dem Werfen einer Exception endet.

Davon gibt es folgende Ausnahmen:

  • Die Lebensdauer eines temporären Objekts kann durch Bindung an eine Referenz verlängert werden, siehe Referenzinitialisierung für Details.
  • Die Lebensdauer eines temporären Objekts, das bei der Auswertung der Default-Argumente eines Default- oder Copy-Konstruktors zur Initialisierung oder Kopie eines Array-Elements erzeugt wird, endet bevor das nächste Element des Arrays mit der Initialisierung beginnt.
  • Die Lebensdauer eines temporären Objekts, das in einer structured binding Deklaration erstellt wird (eingeführt durch den Initialisierer für eine Variable mit eindeutigem Namen), wird bis zum Ende der structured binding Deklaration verlängert.
(since C++17)
  • Die Lebensdauer eines temporären Objekts, das im range-initializer einer range- for Anweisung erstellt wird und andernfalls am Ende des range-initializer zerstört werden würde, wird bis zum Ende des Schleifenkörpers verlängert.
(since C++23)

Speicherwiederverwendung

Ein Programm ist nicht verpflichtet, den Destruktor eines Objekts aufzurufen, um dessen Lebensdauer zu beenden, wenn das Objekt trivially-destructible ist (zu beachten ist, dass das korrekte Verhalten des Programms vom Destruktor abhängen kann). Wenn ein Programm jedoch die Lebensdauer eines nicht trivial destruierbaren Objekts, das eine Variable ist, explizit beendet, muss es sicherstellen, dass ein neues Objekt desselben Typs an derselben Stelle konstruiert wird (z.B. durch Placement new ), bevor der Destruktor implizit aufgerufen werden darf, d.h. aufgrund von Gültigkeitsbereichsende oder Exception für automatische Objekte , aufgrund von Thread-Ende für thread-lokale Objekte, (since C++11) oder aufgrund von Programmende für statische Objekte; andernfalls ist das Verhalten undefiniert.

class T {}; // trivial
struct B
{
    ~B() {} // nicht-trivial
};
void x()
{
    long long n; // automatisch, trivial
    new (&n) double(3.14); // Wiederverwendung mit anderem Typ in Ordnung
} // in Ordnung
void h()
{
    B b; // automatisch nicht-trivial destruierbar
    b.~B(); // Lebensdauer beendet (nicht erforderlich, da keine Seiteneffekte)
    new (&b) T; // falscher Typ: in Ordnung bis der Destruktor aufgerufen wird
} // Destruktor wird aufgerufen: undefiniertes Verhalten

Es ist undefiniertes Verhalten, Speicher wiederzuverwenden, der von einem const vollständigen Objekt mit statischer , thread-lokaler, (since C++11) oder automatischer Speicherdauer belegt ist oder war, da solche Objekte in schreibgeschütztem Speicher abgelegt sein können:

struct B
{
    B(); // nicht-trivial
    ~B(); // nicht-trivial
};
const B b; // konstant statisch
void h()
{
    b.~B(); // Lebensdauer von b beenden
    new (const_cast<B*>(&b)) const B; // undefiniertes Verhalten: Versuch der Wiederverwendung einer Konstanten
}

Bei der Auswertung eines new -Ausdrucks wird Speicher als wiederverwendet betrachtet, nachdem er von der Allokierungsfunktion zurückgegeben wurde, aber vor der Auswertung des Initialisierers des new-Ausdrucks:

struct S
{
    int m;
};
void f()
{
    S x{1};
    new(&x) S(x.m); // undefiniertes Verhalten: der Speicher wird wiederverwendet
}

Wenn ein neues Objekt an der Adresse erstellt wird, die von einem anderen Objekt belegt war, dann zeigen alle Zeiger, Referenzen und der Name des ursprünglichen Objekts automatisch auf das neue Objekt und können, sobald die Lebensdauer des neuen Objekts beginnt, zur Manipulation des neuen Objekts verwendet werden, aber nur wenn das ursprüngliche Objekt transparent durch das neue Objekt ersetzbar ist.

Wenn alle folgenden Bedingungen erfüllt sind, ist Objekt x transparent ersetzbar durch Objekt y :

  • Der Speicher für y überlagert exakt den Speicherbereich, den x belegte.
  • y ist vom gleichen Typ wie x (unter Ignorierung der top-level cv-Qualifizierer).
  • x ist kein vollständiges const-Objekt.
  • Weder x noch y ist ein Basisklassen-Subobjekt , oder ein Member-Subobjekt deklariert mit [[ no_unique_address ]] (seit C++20) .
  • Eine der folgenden Bedingungen ist erfüllt:
  • x und y sind beide vollständige Objekte.
  • x und y sind direkte Unterobjekte von Objekten ox bzw. oy , und ox ist transparent ersetzbar durch oy .
struct C
{
    int i;
    void f();
    const C& operator=(const C&);
};
const C& C::operator=(const C& other)
{
    if (this != &other)
    {
        this->~C();          // Lebensdauer von *this endet
        new (this) C(other); // neues Objekt vom Typ C erstellt
        f();                 // wohldefiniert
    }
    return *this;
}
C c1;
C c2;
c1 = c2; // wohldefiniert
c1.f();  // wohldefiniert; c1 referenziert ein neues Objekt vom Typ C

Wenn die oben aufgeführten Bedingungen nicht erfüllt sind, kann dennoch ein gültiger Zeiger auf das neue Objekt erhalten werden, indem die Zeigeroptimierungsbarriere std::launder angewendet wird:

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(); // undefined behavior:
    // the new A object is a base subobject, while the old one is a complete object
    int m = std::launder(&i)->transmogrify(); // OK
    assert(m + n == 3);
}
(seit C++17)

Ebenso, wenn ein Objekt im Speicher eines Klassenmembers oder Array-Elements erstellt wird, ist das erstellte Objekt nur ein Unterobjekt (Member oder Element) des umschließenden Objekts des ursprünglichen Objekts, wenn:

  • die Lebensdauer des enthaltenden Objekts hat begonnen und ist noch nicht beendet
  • der Speicher für das neue Objekt überlagert exakt den Speicher des ursprünglichen Objekts
  • das neue Objekt ist vom gleichen Typ wie das ursprüngliche Objekt (unter Ignorierung von CV-Qualifizierern).

Andernfalls kann der Name des ursprünglichen Unterobjekts nicht verwendet werden, um auf das neue Objekt zuzugreifen, ohne std::launder :

(seit C++17)

Speicherbereitstellung

Als Sonderfall können Objekte in Arrays von unsigned char oder std::byte (seit C++17) erstellt werden (in diesem Fall wird gesagt, dass das Array Speicher bereitstellt für das Objekt), falls

  • die Lebensdauer des Arrays hat begonnen und ist noch nicht beendet
  • der Speicher für das neue Objekt passt vollständig in das Array
  • es gibt kein Array-Objekt, das diese Einschränkungen erfüllt und innerhalb des Arrays verschachtelt ist

Wenn dieser Teil des Arrays zuvor Speicher für ein anderes Objekt bereitgestellt hat, endet die Lebensdauer dieses Objekts, weil sein Speicher wiederverwendet wurde, jedoch endet die Lebensdauer des Arrays selbst nicht (sein Speicher wird nicht als wiederverwendet betrachtet).

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 stellt Speicher bereit
    char *c = new (au.data) char(); // OK, beendet Lebensdauer von *p
    char *d = new (au.data + 1) char();
    return *c + *d; // OK
}

Zugriff außerhalb der Lebensdauer

Bevor die Lebensdauer eines Objekts begonnen hat, aber nachdem der Speicher, den das Objekt belegen wird, allokiert wurde, oder nachdem die Lebensdauer eines Objekts beendet wurde und bevor der Speicher, den das Objekt belegt hat, wiederverwendet oder freigegeben wird, sind die Verhaltensweisen der folgenden Verwendungen des glvalue-Ausdrucks, der dieses Objekt identifiziert, undefiniert, es sei denn, das Objekt wird gerade konstruiert oder destruiert (ein separater Regelkatalog gilt):

  1. Zugriff auf das Objekt.
  2. Zugriff auf ein nicht-statisches Datenelement oder Aufruf einer nicht-statischen Memberfunktion.
  3. Binden einer Referenz an ein Subobjekt einer virtuellen Basisklasse.
  4. dynamic_cast oder typeid Ausdrücke.

Die oben genannten Regeln gelten auch für Zeiger (das Binden einer Referenz an eine virtuelle Basis wird durch implizite Konvertierung in einen Zeiger auf eine virtuelle Basis ersetzt), mit zwei zusätzlichen Regeln:

  1. static_cast eines Zeigers auf Speicher ohne Objekt ist nur erlaubt, wenn zu (möglicherweise cv-qualifiziertem) void * gecastet wird.
  2. Zeiger auf Speicher ohne Objekt, die zu möglicherweise cv-qualifiziertem void * gecastet wurden, können nur zu Zeigern auf möglicherweise cv-qualifiziertes char , oder möglicherweise cv-qualifiziertes unsigned char , oder möglicherweise cv-qualifiziertes std::byte (seit C++17) static_cast werden.

Während der Konstruktion und Destruktion ist es generell erlaubt, nicht-statische Memberfunktionen aufzurufen, auf nicht-statische Datenelemente zuzugreifen und typeid sowie dynamic_cast zu verwenden. Allerdings sind, weil die Lebensdauer entweder noch nicht begonnen hat (während der Konstruktion) oder bereits beendet ist (während der Destruktion), nur bestimmte Operationen erlaubt. Für eine Einschränkung siehe virtuelle Funktionsaufrufe während Konstruktion und Destruktion .

Hinweise

Bis zur Lösung von CWG Issue 2256 gelten unterschiedliche Regeln für das Lebensende zwischen Nicht-Klassenobjekten (Ende der Speicherdauer) und Klassenobjekten (umgekehrte Reihenfolge der Konstruktion):

struct A
{
    int* p;
    ~A() { std::cout << *p; } // undefiniertes Verhalten seit CWG2256: n überlebt a nicht
                              // wohl-definiert bis CWG2256: gibt 123 aus
};
void f()
{
    A a;
    int n = 123; // wenn n a nicht überlebt, hätte dies optimiert werden können (toter Speicher)
    a.p = &n;
}

Bis zur Lösung von RU007 verhindert ein nicht-statisches Mitglied eines const-qualifizierten Typs oder eines Referenztyps, dass sein enthaltendes Objekt transparent ersetzbar ist, was std::vector und std::deque schwer implementierbar macht:

struct X { const int n; };
union U { X x; float f; };
void tong()
{
    U u = { {1} };
    u.f = 5.f;                          // OK: erzeugt neues Subobjekt von 'u'
    X *p = new (&u.x) X {2};            // OK: erzeugt neues Subobjekt von 'u'
    assert(p->n == 2);                  // OK
    assert(u.x.n == 2);                 // undefiniert bis RU007:
                                        // 'u.x' benennt nicht das neue Subobjekt
    assert(*std::launder(&u.x.n) == 2); // OK auch bis RU007
}

Fehlerberichte

Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR Angewendet auf Verhalten wie veröffentlicht Korrigiertes Verhalten
CWG 119 C++98 ein Objekt eines Klassentyps mit einem nicht-trivialen Konstruktor konnte
seine Lebensdauer erst beginnen, wenn der Konstruktoraufruf abgeschlossen war
Lebensdauer beginnt auch
bei anderen Initialisierungen
CWG 201 C++98 Lebensdauer eines temporären Objekts in einem Standardargument
eines Standardkonstruktors musste enden, wenn
die Initialisierung des Arrays abgeschlossen ist
Lebensdauer endet vor
der Initialisierung des nächsten
Elements (löst auch
CWG Issue 124 )
CWG 274 C++98 ein Lvalue, das ein Objekt außerhalb der Lebensdauer bezeichnet, konnte
nur dann als Operand von static_cast verwendet werden, wenn die Konvertierung
letztendlich zu cv-unqualifiziertem char & oder unsigned char & war
cv-qualifizierte char &
und unsigned char &
ebenfalls erlaubt
CWG 597 C++98 die folgenden Verhaltensweisen waren undefiniert:
1. ein Zeiger auf ein Objekt außerhalb der Lebensdauer wird implizit
in einen Zeiger auf eine nicht-virtuelle Basisklasse konvertiert
2. ein Lvalue, das auf ein Objekt außerhalb der Lebensdauer verweist,
wird an eine Referenz auf eine nicht-virtuelle Basisklasse gebunden
3. ein Lvalue, das auf ein Objekt außerhalb der Lebensdauer verweist, wird
als Operand eines static_cast verwendet (mit wenigen Ausnahmen)
als wohldefiniert festgelegt
CWG 2012 C++98 Lebensdauer von Referenzen war spezifiziert, um der Speicherdauer zu entsprechen,
was erforderte, dass externe Referenzen lebendig sind, bevor ihre Initialisierer ausgeführt werden
Lebensdauer beginnt
bei der Initialisierung
CWG 2107 C++98 die Lösung von CWG Issue 124 wurde nicht auf Kopierkonstruktoren angewendet angewendet
CWG 2256 C++98 Lebensdauer von trivial zerstörbaren Objekten war inkonsistent mit anderen Objekten konsistent gemacht
CWG 2470 C++98 mehr als ein Array konnte Speicher für dasselbe Objekt bereitstellen nur eines stellt bereit
CWG 2489 C++98 char [ ] kann keinen Speicher bereitstellen, aber Objekte
konnten implizit innerhalb seines Speichers erstellt werden
Objekte können nicht
implizit innerhalb
des Speichers von char [ ] erstellt werden
CWG 2527 C++98 wenn ein Destruktor aufgrund von Wiederverwendung des Speichers nicht aufgerufen wird und das
Programm von seinen Nebeneffekten abhängt, war das Verhalten undefiniert
das Verhalten ist in
diesem Fall wohldefiniert
CWG 2721 C++98 der genaue Zeitpunkt der Speicherwiederverwendung war unklar für Placement- new klargestellt
CWG 2849 C++23 Funktionsparameterobjekte wurden als temporäre
Objekte für die Lebensdauerverlängerung von temporären Objekten
in Range- for -Schleifen betrachtet
nicht als temporäre
Objekte betrachtet
CWG 2854 C++98 Ausnahmeobjekte waren temporäre Objekte sie sind keine
temporären Objekte
CWG 2867 C++17 die Lebensdauer von temporären Objekten, die in
strukturierten Bindungsdeklarationen erstellt wurden, wurde nicht verlängert
bis zum Ende
der Deklaration verlängert
P0137R1 C++98 Erstellen eines Objekts in einem Array von unsigned char wiederverwendete seinen Speicher sein Speicher wird nicht wiederverwendet
P0593R6 C++98 ein Pseudo-Destruktoraufruf hatte keine Effekte er zerstört das Objekt
P1971R0 C++98 ein nicht-statisches Datenelement eines const-qualifizierten Typs oder eines Referenztyps
verhinderte, dass sein enthaltendes Objekt transparent ersetzbar ist
Einschränkung entfernt
P2103R0 C++98 transparente Ersetzbarkeit erforderte nicht die Beibehaltung der ursprünglichen Struktur erfordert

Referenzen

  • C++23-Standard (ISO/IEC 14882:2024):
  • 6.7.3 Objektlebensdauer [basic.life]
  • 11.9.5 Konstruktion und Destruktion [class.cdtor]
  • C++20-Standard (ISO/IEC 14882:2020):
  • 6.7.3 Objektlebensdauer [basic.life]
  • 11.10.4 Konstruktion und Destruktion [class.cdtor]
  • C++17-Standard (ISO/IEC 14882:2017):
  • 6.8 Objektlebensdauer [basic.life]
  • 15.7 Konstruktion und Destruktion [class.cdtor]
  • C++14-Standard (ISO/IEC 14882:2014):
  • 3 Objektlebensdauer [basic.life]
  • 12.7 Konstruktion und Destruktion [class.cdtor]
  • C++11-Standard (ISO/IEC 14882:2011):
  • 3.8 Objektlebensdauer [basic.life]
  • 12.7 Konstruktion und Destruktion [class.cdtor]
  • C++03-Standard (ISO/IEC 14882:2003):
  • 3.8 Objektlebensdauer [basic.life]
  • 12.7 Konstruktion und Destruktion [class.cdtor]
  • C++98-Standard (ISO/IEC 14882:1998):
  • 3.8 Objektlebensdauer [basic.life]
  • 12.7 Konstruktion und Destruktion [class.cdtor]

Siehe auch

C-Dokumentation für Lifetime