Lifetime
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:
- Speicher mit der richtigen Ausrichtung und Größe für seinen Typ wird beschafft, und
- seine Initialisierung (falls vorhanden) ist abgeschlossen (einschließlich default initialization über keinen Konstruktor oder trivial default constructor ), außer dass
-
- 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) |
|
(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
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.
|
(since C++17) |
|
(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):
- Zugriff auf das Objekt.
- Zugriff auf ein nicht-statisches Datenelement oder Aufruf einer nicht-statischen Memberfunktion.
- Binden einer Referenz an ein Subobjekt einer virtuellen Basisklasse.
-
dynamic_castodertypeidAusdrü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:
-
static_casteines Zeigers auf Speicher ohne Objekt ist nur erlaubt, wenn zu (möglicherweise cv-qualifiziertem) void * gecastet wird. -
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_castwerden.
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
|