Namensräume
Varianten
Aktionen

Lebensdauer

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

Jedes Objekt und jede Referenz hat eine Lebensdauer, die eine Laufzeiteigenschaft ist: für jedes Objekt oder jede Referenz gibt es einen Ausführungspunkt eines Programms, an dem ihre Lebensdauer beginnt, und es gibt einen Moment, an dem sie endet.

Die Lebensdauer eines Objekts beginnt, wenn

  • Speicher mit der richtigen Ausrichtung und Größe für seinen Typ bereitgestellt wird, und
  • seine Initialisierung (falls vorhanden) abgeschlossen ist (einschließlich Standardinitialisierung durch keinen Konstruktor oder trivialen Standardkonstruktor), außer dass
  • wenn das Objekt ein Union-Mitglied oder ein Teil davon ist, seine Lebensdauer nur beginnt, wenn dieses Union-Mitglied das initialisierte Mitglied in der Union ist oder es aktiviert wird,
  • wenn das Objekt in einem Union-Objekt verschachtelt ist, seine Lebensdauer beginnen kann, wenn das enthaltende Union-Objekt durch eine triviale spezielle Memberfunktion zugewiesen oder konstruiert wird,
  • die Lebensdauer eines Array-Objekts kann auch beginnen, wenn es von std::allocator::allocate alloziert wird.

Einige Operationen erzeugen implizit Objekte von Typen mit impliziter Lebensdauer in einem gegebenen Speicherbereich und starten deren Lebensdauer. Wenn ein Teilobjekt eines implizit erzeugten Objekts kein Typ mit impliziter Lebensdauer ist, beginnt seine Lebensdauer nicht implizit.

Die Lebensdauer eines Objekts endet, wenn

  • wenn es von einem Nicht-Klassen-Typ ist, das Objekt zerstört wird (möglicherweise durch einen Pseudo-Destruktor-Aufruf), oder
  • wenn es von einem Klassentyp ist, der Aufruf des Destruktors beginnt, oder
  • der Speicher, den das Objekt belegt, freigegeben wird oder von einem Objekt wiederverwendet wird, das nicht darin verschachtelt ist.

Die Lebensdauer eines Objekts ist gleich der Lebensdauer seines Speichers oder darin verschachtelt, siehe Speicherdauer.

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

Hinweis: Die Lebensdauer des referenzierten Objekts kann vor dem Ende der Lebensdauer der Referenz enden, was hängende Referenzen ermöglicht.

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

Inhalt

[bearbeiten] Lebensdauer temporärer Objekte

Temporäre Objekte werden erstellt, wenn ein prvalue materialisiert wird, damit er als glvalue verwendet werden kann, was(seit C++17) in den folgenden Situationen geschieht

(seit C++11)
(seit C++11)
  • Kopierinitialisierung, die eine Konvertierung des Initialisierers erfordert,
  • Referenzinitialisierung an einen anderen, aber konvertierbaren Typ oder an ein Bitfeld.
(bis C++17)

Die Materialisierung eines temporären Objekts wird im Allgemeinen so lange wie möglich verzögert, um die Erzeugung unnötiger temporärer Objekte zu vermeiden: siehe Kopielision.

(seit C++17)


Wenn ein Objekt vom Typ T an einen potenziell ausgewerteten Funktionsaufruf übergeben oder von ihm zurückgegeben wird, und wenn T einer der folgenden Typen ist, dürfen Implementierungen temporäre Objekte erstellen, um den Funktionsparameter oder das Rückgabeobjekt zu speichern

(seit C++26)
  • ein Klassentyp, der alle folgenden Bedingungen erfüllt
    • T hat mindestens einen zulässigen Kopier- oder Verschiebe-Konstruktor.
    • Jeder zulässige Kopier-/Verschiebe-Konstruktor von T ist trivial.
    • Der Destruktor von T ist entweder trivial oder gelöscht.

Das temporäre Objekt wird aus dem Funktionsargument bzw. Rückgabewert konstruiert, und der Parameter oder das Rückgabeobjekt der Funktion wird so initialisiert, als ob durch Verwendung des zulässigen trivialen Konstruktors das temporäre Objekt kopiert würde (auch wenn dieser Konstruktor unzugänglich ist oder nicht zur Auswahl für die Durchführung einer Kopie oder Verschiebung des Objekts stünde).

(bis C++26)

Die temporären Objekte werden wie folgt erstellt

  • Das erste solche temporäre Objekt wird aus dem Funktionsargument bzw. Rückgabewert konstruiert.
  • Jedes nachfolgende temporäre Objekt wird aus dem vorherigen initialisiert, als ob durch direkte Initialisierung, wenn T ein Skalar-Typ ist, andernfalls durch Verwendung eines zulässigen trivialen Konstruktors.
  • Der Funktionsparameter oder das Rückgabeobjekt wird aus dem endgültigen temporären Objekt initialisiert, als ob durch direkte Initialisierung, wenn T ein Skalar-Typ ist, andernfalls durch Verwendung eines zulässigen trivialen Konstruktors.

In allen Fällen wird der zulässige Konstruktor auch dann verwendet, wenn dieser Konstruktor unzugänglich ist oder nicht zur Auswahl für die Durchführung einer Kopie oder Verschiebung des Objekts stünde.

(seit C++26)

Diese Freiheit wird gewährt, um Objekte in Registern an Funktionen übergeben oder von ihnen zurückgeben zu können.

(seit C++17)

Alle temporären Objekte werden als letzter Schritt bei der Auswertung des vollständigen Ausdrucks zerstört, der (lexikalisch) den Punkt, an dem sie erstellt wurden, enthält. Wenn mehrere temporäre Objekte erstellt wurden, werden sie in umgekehrter Reihenfolge ihrer Erstellung zerstört. Dies gilt auch, wenn die Auswertung mit dem Auslösen einer Ausnahme endet.

Es gibt die folgenden Ausnahmen davon

  • 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 Standardargumente eines Standard- oder Kopierkonstruktors für die Initialisierung oder Kopie eines Array-Elements erstellt wird, endet vor Beginn der Initialisierung des nächsten Array-Elements.
  • Die Lebensdauer eines temporären Objekts, das in einer strukturierten Bindungsdeklaration (eingeführt durch den Initialisierer einer Variablen mit eindeutigem Namen) erstellt wird, wird bis zum Ende der strukturierten Bindungsdeklaration verlängert.
(seit C++17)
  • Die Lebensdauer eines temporären Objekts, das im Bereichinitialisierer einer Bereichs-for-Anweisung erstellt wird und das andernfalls am Ende des Bereichinitialisierers zerstört würde, wird bis zum Ende des Schleifenkörpers verlängert.
(seit C++23)

[bearbeiten] Speicherwiederverwendung

Ein Programm ist nicht verpflichtet, den Destruktor eines Objekts aufzurufen, um seine Lebensdauer zu beenden, wenn das Objekt trivial destruierbar ist (seien Sie vorsichtig, dass das korrekte Verhalten des Programms vom Destruktor abhängen kann). Wenn jedoch ein Programm die Lebensdauer eines nicht-trivial destruierbaren Objekts, das eine Variable ist, explizit beendet, muss sichergestellt werden, dass ein neues Objekt desselben Typs "in-place" (z. B. über Placement new) konstruiert wird, bevor der Destruktor implizit aufgerufen werden kann, d. h. aufgrund des Geltungsbereich-Endes oder einer Ausnahme für automatische Objekte, aufgrund des Thread-Endes für Thread-lokale Objekte,(seit C++11) oder aufgrund des Programm-Endes für statische Objekte; andernfalls ist das Verhalten undefiniert.

class T {}; // trivial
 
struct B
{
    ~B() {} // non-trivial
};
 
void x()
{
    long long n; // automatic, trivial
    new (&n) double(3.14); // reuse with a different type okay
} // okay
 
void h()
{
    B b; // automatic non-trivially destructible
    b.~B(); // end lifetime (not required, since no side-effects)
    new (&b) T; // wrong type: okay until the destructor is called
} // destructor is called: undefined behavior

Es ist undefiniertes Verhalten, Speicher wiederzuverwenden, der von einem konstanten vollständigen Objekt mit statischer, Thread-lokaler,(seit C++11) oder automatischer Speicherdauer belegt wird oder wurde, da solche Objekte in schreibgeschütztem Speicher abgelegt sein können.

struct B
{
    B(); // non-trivial
    ~B(); // non-trivial
};
const B b; // const static
 
void h()
{
    b.~B(); // end the lifetime of b
    new (const_cast<B*>(&b)) const B; // undefined behavior: attempted reuse of a const
}

Bei der Auswertung eines new-Ausdrucks wird der Speicher als wiederverwendet betrachtet, nachdem er von der Allokationsfunktion 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); // undefined behavior: the storage is reused
}

Wenn ein neues Objekt an der Adresse erstellt wird, die zuvor von einem anderen Objekt belegt wurde, dann beziehen sich 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 ersetzt werden kann.

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

  • Der Speicher für y überlappt genau den Speicherort, den x belegt hat.
  • y hat denselben Typ wie x (oberste cv-Qualifizierer ignoriert).
  • x ist kein vollständiges const-Objekt.
  • Weder x noch y ist ein Basisklassen-Teilobjekt, oder ein Mitgliedsteilobjekt, 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 Teilobjekte von Objekten ox und oy bzw. und ox ist transparent durch oy ersetzbar.
struct C
{
    int i;
    void f();
    const C& operator=(const C&);
};
 
const C& C::operator=(const C& other)
{
    if (this != &other)
    {
        this->~C();          // lifetime of *this ends
        new (this) C(other); // new object of type C created
        f();                 // well-defined
    }
    return *this;
}
 
C c1;
C c2;
c1 = c2; // well-defined
c1.f();  // well-defined; c1 refers to a new object of type C

Wenn die oben aufgeführten Bedingungen nicht erfüllt sind, kann durch Anwendung der Zeigeroptimierungsbarriere std::launder immer noch ein gültiger Zeiger auf das neue Objekt erhalten werden.

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)

Ähnlich gilt, wenn ein Objekt im Speicher eines Klassenmitglieds oder eines Array-Elements erstellt wird, ist das erstellte Objekt nur dann ein Teilobjekt (Mitglied oder Element) des enthaltenden Objekts des ursprünglichen Objekts, wenn

  • die Lebensdauer des enthaltenden Objekts begonnen und nicht beendet hat
  • der Speicher für das neue Objekt genau den Speicher des ursprünglichen Objekts überlappt
  • das neue Objekt denselben Typ hat wie das ursprüngliche Objekt (cv-Qualifizierung ignoriert).

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

(seit C++17)

[bearbeiten] Bereitstellen von Speicher

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 für das Objekt bereitstellt), wenn

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

Wenn dieser Teil des Arrays zuvor Speicher für ein anderes Objekt bereitgestellt hat, endet die Lebensdauer dieses Objekts, da sein Speicher wiederverwendet wurde. Die Lebensdauer des Arrays selbst endet jedoch 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 provides storage
    char *c = new (au.data) char(); // OK, ends lifetime of *p
    char *d = new (au.data + 1) char();
    return *c + *d; // OK
}

[bearbeiten] Zugriff außerhalb der Lebensdauer

Vor Beginn der Lebensdauer eines Objekts, aber nachdem der Speicher, den das Objekt belegen wird, allokiert wurde, oder nach dem Ende der Lebensdauer eines Objekts 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 zerstört (ein separater Regelsatz gilt).

  1. Lvalue-zu-Rvalue-Konvertierung (z. B. Funktionsaufruf an eine Funktion, die einen Wert als Parameter nimmt).
  2. Zugriff auf ein nicht-statisches Datenmitglied oder Aufruf einer nicht-statischen Memberfunktion.
  3. Binden einer Referenz an ein virtuelles Basisklassen-Teilobjekt.
  4. dynamic_cast oder typeid Ausdrücke.

Die obigen Regeln gelten auch für Zeiger (das Binden einer Referenz an eine virtuelle Basis wird durch die 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 der Cast zu (möglicherweise cv-qualifiziertem) void* erfolgt.
  2. Zeiger auf Speicher ohne Objekt, die zu (möglicherweise cv-qualifiziertem) void* gecastet wurden, können nur zu (möglicherweise cv-qualifizierten) char, oder (möglicherweise cv-qualifizierten) unsigned char, oder (möglicherweise cv-qualifiziertem) std::byte(seit C++17) gecastet werden.

Während der Konstruktion und Zerstörung ist es generell erlaubt, nicht-statische Memberfunktionen aufzurufen, auf nicht-statische Datenmitglieder zuzugreifen und typeid sowie dynamic_cast zu verwenden. Da die Lebensdauer jedoch entweder noch nicht begonnen hat (während der Konstruktion) oder bereits beendet ist (während der Zerstörung), sind nur bestimmte Operationen erlaubt. Eine Einschränkung finden Sie unter virtuelle Funktionsaufrufe während der Konstruktion und Zerstörung.

[bearbeiten] Hinweise

Bis zur Behebung von CWG-Problem 2256 sind die Regeln für das Ende der Lebensdauer für Nicht-Klassen-Objekte (Ende der Speicherdauer) und Klassenobjekte (umgekehrte Reihenfolge der Konstruktion) unterschiedlich.

struct A
{
    int* p;
    ~A() { std::cout << *p; } // undefined behavior since CWG2256: n does not outlive a
                              // well-defined until CWG2256: prints 123
};
 
void f()
{
    A a;
    int n = 123; // if n did not outlive a, this could have been optimized out (dead store)
    a.p = &n;
}

Bis zur Behebung von RU007 verhindert ein nicht-statisches Mitglied eines const-qualifizierten Typs oder eines Referenztyps, dass sein enthaltendes Objekt transparent ersetzbar ist, was die Implementierung von std::vector und std::deque erschwert.

struct X { const int n; };
union U { X x; float f; };
 
void tong()
{
    U u = { {1} };
    u.f = 5.f;                          // OK: creates new subobject of 'u'
    X *p = new (&u.x) X {2};            // OK: creates new subobject of 'u'
    assert(p->n == 2);                  // OK
    assert(u.x.n == 2);                 // undefined until RU007:
                                        // 'u.x' does not name the new subobject
    assert(*std::launder(&u.x.n) == 2); // OK even until RU007
}

[bearbeiten] 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 kann
seine Lebensdauer erst beginnen, wenn der Konstruktoraufruf abgeschlossen ist
die Lebensdauer begann auch
für andere Initialisierungen
CWG 201 C++98 Lebensdauer eines temporären Objekts in einem Standardargument
eines Standardkonstruktors musste enden
wenn die Initialisierung des Arrays abgeschlossen war
die Lebensdauer endet vor
der Initialisierung des nächsten
Elements (löst auch
CWG-Problem 124)
CWG 274 C++98 ein Lvalue, das ein Objekt außerhalb der Lebensdauer bezeichnet, konnte
als Operand von static_cast verwendet werden, nur wenn die Konvertierung
letztendlich zu cv-unqualifiziertem char& oder unsigned char& war
cv-qualifiziertem char&
und unsigned char&
auch 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 verwendet
als Operand eines static_cast (mit wenigen Ausnahmen)
wurde wohlformuliert
CWG 2012 C++98 die Lebensdauer von Referenzen wurde so spezifiziert, dass sie der Speicherdauer entspricht,
wodurch gefordert wird, dass externe Referenzen vor dem Ausführen ihrer Initialisierer leben
die Lebensdauer beginnt
bei der Initialisierung
CWG 2107 C++98 die Behebung von CWG-Problem 124 wurde nicht auf Kopierkonstruktoren angewendet. angewendet
CWG 2256 C++98 die Lebensdauer von trivial destruierbaren Objekten stimmte nicht mit anderen Objekten überein. konsistent gemacht
CWG 2470 C++98 mehrere Arrays konnten Speicher für dasselbe Objekt bereitstellen nur eins stellt bereit
CWG 2489 C++98 char[] kann keinen Speicher bereitstellen, aber Objekte
konnten innerhalb seines Speichers implizit erstellt werden
Objekte können nicht
innerhalb
des Speichers von char[] implizit erstellt werden
CWG 2527 C++98 wenn ein Destruktor nicht wegen Speicherwiederverwendung aufgerufen wurde und das
Programm von seinen Seiteneffekten abhängt, war das Verhalten undefiniert
das Verhalten ist in diesem Fall wohldefiniert.
CWG 2721
der genaue Zeitpunkt der Speicherwiederverwendung war bei Placement new unklar. C++98 CWG 2849 wurde klargestellt
Funktionsparameterobjekte wurden als temporäre C++23 Objekte für die Lebensdauerverlängerung von temporären Objekten in Bereichs-for-Schleifen betrachtet
nicht als
temporäre Objekte
betrachtet
CWG 2854 C++98 Ausnahmeobjekte waren temporäre Objekte sie sind es nicht
betrachtet
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 das Erstellen eines Objekts in einem Array von unsigned char hat seinen Speicher wiederverwendet sein Speicher wird nicht wiederverwendet
P0593R6 C++98 ein Pseudo-Destruktoraufruf hatte keine Auswirkungen er zerstört das Objekt
P1971R0 C++98 ein nicht-statisches Datenmitglied eines const-qualifizierten Typs oder eines Referenztyps
verhinderte, dass sein enthaltendes Objekt transparent ersetzbar war
Beschränkung aufgehoben
P2103R0 C++98 die transparente Ersetzbarkeit erforderte nicht die Beibehaltung der ursprünglichen Struktur requires

[bearbeiten] Referenzen

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

[bearbeiten] Siehe auch

C-Dokumentation für Lebensdauer