Lifetime
Каждый объект и ссылка имеют время жизни , которое является свойством времени выполнения: для любого объекта или ссылки существует момент выполнения программы, когда их время жизни начинается, и момент, когда оно заканчивается.
Время жизни объекта начинается, когда:
- получено хранилище с надлежащим выравниванием и размером для его типа, и
- его инициализация (если требуется) завершена (включая default initialization через отсутствие конструктора или trivial default constructor ), за исключением того, что
-
- если объект является членом объединения или подобъектом такового, его время жизни начинается только если этот член объединения является инициализированным членом в объединении, или он становится активным,
- если объект вложен в объект объединения, его время жизни может начаться если содержащий объект объединения присваивается или конструируется тривиальной специальной функцией-членом,
- время жизни объекта массива также может начаться если он выделен с помощью std::allocator::allocate .
Некоторые операции неявно создают объекты типов с неявным временем жизни в заданной области памяти и начинают их время жизни. Если подобъект неявно созданного объекта не относится к типу с неявным временем жизни, его время жизни не начинается неявно.
Время жизни объекта заканчивается, когда:
- если это неклассовый тип, объект уничтожается (возможно, через вызов псевдодеструктора), или
- если это классовый тип, начинается вызов деструктора , или
- память, которую занимает объект, освобождается или переиспользуется объектом, который не вложен в него.
Время жизни объекта равно или вложено во время жизни его хранилища, см. storage duration .
Время жизни ссылки начинается после завершения её инициализации и заканчивается так, как если бы она была скалярным объектом.
Примечание: время жизни объекта, на который ссылается ссылка, может завершиться до окончания времени жизни ссылки, что делает возможным возникновение висячих ссылок .
Время жизни нестатических элементов данных и подобъектов базовых классов начинается и заканчивается в соответствии с порядком инициализации класса .
Содержание |
Время жизни временных объектов
Временные объекты создаются когда prvalue материализуется для использования в качестве glvalue, что происходит (начиная с C++17) в следующих ситуациях:
|
(начиная с C++11) |
|
(until C++17) | ||
Материализация временного объекта обычно откладывается как можно дольше, чтобы избежать создания ненужных временных объектов: см. copy elision . |
(since C++17) |
|
Когда объект типа
Эта возможность предоставлена для разрешения передачи объектов в функции и возврата из функций через регистры. |
(since C++17) |
Все временные объекты уничтожаются как последний шаг при вычислении полного выражения , которое (лексически) содержит точку, где они были созданы, и если было создано несколько временных объектов, они уничтожаются в порядке, обратном порядку создания. Это верно даже если вычисление завершается выбрасыванием исключения.
Из этого есть следующие исключения:
- Время жизни временного объекта может быть продлено привязкой к ссылке, подробности смотрите в разделе инициализация ссылки .
- Время жизни временного объекта, созданного при вычислении аргументов по умолчанию конструктора по умолчанию или копирования, используемого для инициализации или копирования элемента массива, завершается до начала инициализации следующего элемента массива.
|
(since C++17) |
|
(since C++23) |
Повторное использование хранилища
Программа не обязана вызывать деструктор объекта для завершения его времени жизни, если объект является тривиально-разрушаемым (следует учитывать, что корректное поведение программы может зависеть от деструктора). Однако если программа явно завершает время жизни нетривиально разрушаемого объекта, который является переменной, она должна обеспечить создание нового объекта того же типа на том же месте (например, с помощью placement new ) до того, как деструктор может быть вызван неявно, т.е. при выходе из области видимости или исключении для автоматических объектов , при завершении потока для thread-local объектов, (since C++11) или при завершении программы для статических объектов; в противном случае поведение не определено.
class T {}; // тривиальный struct B { ~B() {} // нетривиальный }; void x() { long long n; // автоматический, тривиальный new (&n) double(3.14); // повторное использование с другим типом допустимо } // корректно void h() { B b; // автоматический нетривиально разрушаемый b.~B(); // завершение времени жизни (не требуется, так как нет побочных эффектов) new (&b) T; // неверный тип: допустимо до вызова деструктора } // вызывается деструктор: неопределённое поведение
Повторное использование памяти, которая занята или была занята константным полным объектом со статической , поточно-локальной, (since C++11) или автоматической длительностью хранения, является неопределённым поведением, поскольку такие объекты могут храниться в памяти только для чтения:
struct B { B(); // нетривиальный ~B(); // нетривиальный }; const B b; // константный статический void h() { b.~B(); // завершение времени жизни b new (const_cast<B*>(&b)) const B; // неопределенное поведение: попытка повторного использования константы }
При вычислении выражения new память считается переиспользованной после её возврата из функции выделения памяти , но до вычисления инициализатора выражения new:
struct S { int m; }; void f() { S x{1}; new(&x) S(x.m); // неопределённое поведение: память переиспользуется }
Если новый объект создаётся по адресу, который был занят другим объектом, то все указатели, ссылки и имя исходного объекта автоматически будут ссылаться на новый объект и, как только время жизни нового объекта начнётся, могут использоваться для манипулирования новым объектом, но только если исходный объект может быть прозрачно заменён новым объектом.
Если все следующие условия выполняются, объект x является прозрачно заменяемым объектом y :
- Хранилище для y точно перекрывает область памяти, которую x занимал.
- y имеет тот же тип, что и x (игнорируя cv-квалификаторы верхнего уровня).
- x не является полным константным объектом.
-
Ни
x
, ни
y
не являются базовым подобъектом
, или подобъектом-членом, объявленным с
[[ no_unique_address ]](начиная с C++20) . - Удовлетворяется одно из следующих условий:
-
- x и y являются полными объектами.
- x и y являются прямыми подобъектами объектов ox и oy соответственно, и ox может быть прозрачно заменён на oy .
struct C { int i; void f(); const C& operator=(const C&); }; const C& C::operator=(const C& other) { if (this != &other) { this->~C(); // время жизни *this завершается new (this) C(other); // создается новый объект типа C f(); // корректно определено } return *this; } C c1; C c2; c1 = c2; // корректно определено c1.f(); // корректно определено; c1 ссылается на новый объект типа C
|
Если перечисленные выше условия не выполняются, валидный указатель на новый объект всё ещё может быть получен применением барьера оптимизации указателей 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(); // 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); } |
(начиная с C++17) |
Аналогично, если объект создаётся в памяти члена класса или элемента массива, созданный объект является лишь подобъектом (членом или элементом) содержащего объекта исходного объекта, если:
- время жизни содержащего объекта началось и не завершилось
- хранилище для нового объекта точно перекрывает хранилище исходного объекта
- новый объект имеет тот же тип, что и исходный объект (игнорируя cv-квалификаторы).
|
В противном случае имя исходного подобъекта не может быть использовано для доступа к новому объекту без std::launder :
|
(начиная с C++17) |
Предоставление хранилища
В качестве особого случая, объекты могут быть созданы в массивах из unsigned char или std::byte (начиная с C++17) (в этом случае говорят, что массив предоставляет хранилище для объекта), если
- время жизни массива началось и не завершилось
- хранилище для нового объекта полностью помещается в массив
- в массиве нет вложенного объекта массива, удовлетворяющего этим ограничениям.
Если эта часть массива ранее предоставляла память для другого объекта, время жизни этого объекта завершается, поскольку его память была переиспользована, однако время жизни самого массива не завершается (его память не считается переиспользованной).
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 предоставляет память char *c = new (au.data) char(); // OK, завершает время жизни *p char *d = new (au.data + 1) char(); return *c + *d; // OK }
Доступ за пределами времени жизни
До начала времени жизни объекта, но после того, как память, которую объект будет занимать, была выделена, или после завершения времени жизни объекта и до того, как память, которую объект занимал, будет повторно использована или освобождена, поведение следующих использований glvalue-выражения, идентифицирующего этот объект, является неопределенным, если только объект не конструируется или не уничтожается (применяется отдельный набор правил):
- Доступ к объекту.
- Доступ к нестатическому члену данных или вызов нестатической функции-члена.
- Привязка ссылки к подобъекту виртуального базового класса.
-
dynamic_castилиtypeidвыражения.
Вышеуказанные правила применяются и к указателям (привязка ссылки к виртуальному базовому классу заменяется неявным преобразованием в указатель на виртуальный базовый класс) с двумя дополнительными правилами:
-
static_castуказателя на область памяти без объекта разрешен только при приведении к (возможно cv-квалифицированному) void * . -
Указатели на область памяти без объекта, приведенные к возможно cv-квалифицированному
void
*
, могут быть
static_castтолько к указателям на возможно cv-квалифицированный char , или возможно cv-квалифицированный unsigned char , или возможно cv-квалифицированный std::byte (начиная с C++17) .
Во время конструирования и деструкции обычно разрешено вызывать нестатические функции-члены, обращаться к нестатическим членам данных и использовать
typeid
и
dynamic_cast
. Однако, поскольку время жизни либо еще не началось (во время конструирования), либо уже завершилось (во время деструкции), разрешены только определенные операции. Одно из ограничений см. в разделе
вызовы виртуальных функций во время конструирования и деструкции
.
Примечания
До разрешения CWG issue 2256 , правила завершения времени жизни различаются для неклассовых объектов (окончание времени хранения) и классовых объектов (обратный порядок конструирования):
struct A { int* p; ~A() { std::cout << *p; } // неопределённое поведение начиная с CWG2256: n не переживает a // корректное поведение до CWG2256: выводит 123 }; void f() { A a; int n = 123; // если бы n не переживал a, это могло быть оптимизировано (мёртвое сохранение) a.p = &n; }
До разрешения вопроса RU007 , нестатический член константно-квалифицированного типа или ссылочного типа препятствует прозрачной заменяемости содержащего его объекта, что затрудняет реализацию std::vector и std::deque :
struct X { const int n; }; union U { X x; float f; }; void tong() { U u = { {1} }; u.f = 5.f; // OK: создает новый подобъект 'u' X *p = new (&u.x) X {2}; // OK: создает новый подобъект 'u' assert(p->n == 2); // OK assert(u.x.n == 2); // неопределено до RU007: // 'u.x' не ссылается на новый подобъект assert(*std::launder(&u.x.n) == 2); // OK даже до RU007 }
Отчеты о дефектах
Следующие отчеты об изменениях в поведении, являющиеся дефектными, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 119 | C++98 |
объект типа класса с нетривиальным конструктором может
начать своё время жизни только после завершения вызова конструктора |
время жизни также начинается
при других инициализациях |
| CWG 201 | C++98 |
время жизни временного объекта в аргументе по умолчанию
конструктора по умолчанию должно было завершаться при завершении инициализации массива |
время жизни завершается до
инициализации следующего элемента (также решает CWG issue 124 ) |
| CWG 274 | C++98 |
lvalue, указывающее на объект вне времени жизни, могло быть
использовано как операнд static_cast только если преобразование в конечном счёте было в cv-unqualified char & или unsigned char & |
cv-qualified
char
&
и unsigned char & также разрешены |
| CWG 597 | C++98 |
следующие поведения были неопределёнными:
1. указатель на объект вне времени жизни неявно преобразуется в указатель на невиртуальный базовый класс 2. lvalue, ссылающееся на объект вне времени жизни связывается со ссылкой на невиртуальный базовый класс 3. lvalue, ссылающееся на объект вне времени жизни, используется как операнд static_cast (с несколькими исключениями) |
сделано определённым |
| CWG 2012 | C++98 |
время жизни ссылок было определено как соответствующее времени хранения,
требуя, чтобы внешние ссылки были активны до запуска их инициализаторов |
время жизни начинается
при инициализации |
| CWG 2107 | C++98 | решение CWG issue 124 не применялось к конструкторам копирования | применено |
| CWG 2256 | C++98 | время жизни тривиально деструктируемых объектов было несогласованно с другими объектами | сделано согласованным |
| CWG 2470 | C++98 | более одного массива могли предоставлять хранилище для одного объекта | только один предоставляет |
| CWG 2489 | C++98 |
char
[
]
не может предоставлять хранилище, но объекты
могли быть неявно созданы в его хранилище |
объекты не могут быть
неявно созданы в хранилище char [ ] |
| CWG 2527 | C++98 |
если деструктор не вызывается из-за повторного использования хранилища и
программа зависит от его побочных эффектов, поведение было неопределённым |
поведение является
определённым в этом случае |
| CWG 2721 | C++98 | точный момент времени повторного использования хранилища был неясен для placement new | прояснено |
| CWG 2849 | C++23 |
объекты параметров функций считались временными
объектами для расширения времени жизни временных объектов цикла range- for |
не считаются
временными объектами |
| CWG 2854 | C++98 | объекты исключений были временными объектами |
они не являются
временными объектами |
| CWG 2867 | C++17 |
время жизни временных объектов, созданных в
объявлениях структурированных привязок, не расширялось |
расширено до конца
объявления |
| P0137R1 | C++98 | создание объекта в массиве unsigned char повторно использовало его хранилище | его хранилище не используется повторно |
| P0593R6 | C++98 | вызов псевдодеструктора не имел эффектов | он уничтожает объект |
| P1971R0 | C++98 |
нестатический член данных типа с квалификатором const или ссылочного типа
препятствовал прозрачной замене содержащего его объекта |
ограничение удалено |
| P2103R0 | C++98 | прозрачная заменяемость не требовала сохранения исходной структуры | требует |
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2024):
-
- 6.7.3 Время жизни объекта [basic.life]
-
- 11.9.5 Конструкция и деструкция [class.cdtor]
- Стандарт C++20 (ISO/IEC 14882:2020):
-
- 6.7.3 Время жизни объекта [basic.life]
-
- 11.10.4 Конструкция и деструкция [class.cdtor]
- Стандарт C++17 (ISO/IEC 14882:2017):
-
- 6.8 Время жизни объекта [basic.life]
-
- 15.7 Конструкция и деструкция [class.cdtor]
- Стандарт C++14 (ISO/IEC 14882:2014):
-
- 3 Время жизни объекта [basic.life]
-
- 12.7 Конструкторы и деструкторы [class.cdtor]
- Стандарт C++11 (ISO/IEC 14882:2011):
-
- 3.8 Время жизни объекта [basic.life]
-
- 12.7 Конструкция и деструкция [class.cdtor]
- Стандарт C++03 (ISO/IEC 14882:2003):
-
- 3.8 Время жизни объекта [basic.life]
-
- 12.7 Конструкция и деструкция [class.cdtor]
- Стандарт C++98 (ISO/IEC 14882:1998):
-
- 3.8 Время жизни объекта [basic.life]
-
- 12.7 Конструкция и деструкция [class.cdtor]
Смотрите также
|
C documentation
для
Lifetime
|