Lebensdauer
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) |
|
(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
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.
|
(seit C++17) |
|
(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).
- Lvalue-zu-Rvalue-Konvertierung (z. B. Funktionsaufruf an eine Funktion, die einen Wert als Parameter nimmt).
- Zugriff auf ein nicht-statisches Datenmitglied oder Aufruf einer nicht-statischen Memberfunktion.
- Binden einer Referenz an ein virtuelles Basisklassen-Teilobjekt.
-
dynamic_castodertypeidAusdrü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
-
static_casteines Zeigers auf Speicher ohne Objekt ist nur erlaubt, wenn der Cast zu (möglicherweise cv-qualifiziertem) void* erfolgt. - 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
|