Время жизни
Каждый объект и каждая ссылка имеют время жизни — свойство времени выполнения: для любого объекта или ссылки существует момент выполнения программы, когда начинается время его жизни, и момент, когда оно заканчивается.
Время жизни объекта начинается, когда:
- получено хранилище с подходящим выравниванием и размером для его типа, и
- завершена его инициализация (если она есть), включая инициализацию по умолчанию без конструктора или с помощью тривиального конструктора по умолчанию, за исключением случаев, когда
- объект является членом объединения или его подобъектом: его время жизни начинается, только если этот член объединения является инициализированным членом объединения или становится активным,
- объект вложен в объект-объединение: его время жизни может начаться, если содержащее его объединение присваивается или конструируется тривиальной специальной функцией-членом,
- время жизни объекта-массива также может начаться, если он выделен с помощью
std::allocator::allocate.
Некоторые операции неявно создают объекты типов с неявным временем жизни в заданной области хранилища и начинают их время жизни. Если подобъект неявно созданного объекта не имеет типа с неявным временем жизни, его время жизни не начинается неявно.
Время жизни объекта заканчивается, когда:
- если он имеет неклассовый тип, объект уничтожается (возможно, посредством вызова псевдодеструктора), или
- если он имеет классовый тип, начинается вызов деструктора, или
- хранилище, занимаемое объектом, освобождается или повторно используется объектом, не вложенным в него.
Время жизни объекта совпадает со временем жизни его хранилища или вложено в него; см. длительность хранения.
Время жизни ссылки начинается после завершения её инициализации и заканчивается так же, как время жизни скалярного объекта.
Примечание: время жизни объекта, на который ссылаются, может закончиться раньше времени жизни ссылки, что делает возможными висячие ссылки.
Время жизни нестатических членов данных и базовых подобъектов начинается и заканчивается в соответствии с порядком инициализации класса.
Время жизни временного объекта
Временные объекты создаются при материализации prvalue-выражения, чтобы его можно было использовать как glvalue; это происходит(начиная с C++17) в следующих ситуациях:
| (начиная с C++11) |
| (до C++17) | ||
Материализация временного объекта обычно откладывается как можно дольше, чтобы избежать создания ненужного временного объекта; см. устранение копирования. | (начиная с C++17) |
| Когда объект типа
Это послабление предусмотрено, чтобы разрешить передачу объектов в функции и возврат из функций через регистры. | (начиная с C++17) |
Все временные объекты уничтожаются на последнем этапе вычисления полного выражения, которое (лексически) содержит точку их создания. Если создано несколько временных объектов, они уничтожаются в порядке, обратном порядку создания. Это справедливо, даже если вычисление завершается выбрасыванием исключения.
Существуют следующие исключения:
- Время жизни временного объекта может быть продлено привязкой к ссылке; подробности см. в разделе инициализация ссылки.
- Время жизни временного объекта, созданного при вычислении аргументов по умолчанию конструктора по умолчанию или копирующего конструктора, используемого для инициализации или копирования элемента массива, заканчивается до начала инициализации следующего элемента массива.
| (начиная с C++17) |
| (начиная с C++23) |
Повторное использование хранилища
Программа может завершить время жизни объекта, явно вызвав его деструктор; если у объекта тривиальный деструктор, его время жизни также можно безопасно завершить освобождением хранилища объекта. Однако, если программа явно завершает время жизни объекта-переменной, она должна гарантировать, что новый объект того же типа будет создан на том же месте (например, с помощью размещающего new) до того, как деструктор может быть вызван неявно, то есть при выходе из области видимости или возникновении исключения для автоматических объектов, при завершении потока для локальных объектов потока,(начиная с C++11) или при завершении программы для статических объектов; в противном случае поведение не определено.
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
Повторное использование хранилища, которое занято или было занято полным константным объектом со статической, локальной для потока(начиная с C++11) или автоматической длительностью хранения, приводит к неопределённому поведению, поскольку такие объекты могут храниться в памяти только для чтения:
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
}
При вычислении new-выражения считается, что хранилище повторно используется после его возврата из функции выделения памяти, но до вычисления инициализатора new-выражения:
struct S
{
int m;
};
void f()
{
S x{1};
new(&x) S(x.m); // undefined behavior: the storage is reused
}
Если новый объект создаётся по адресу, который был занят другим объектом, все указатели, ссылки и имя исходного объекта автоматически начинают обозначать новый объект и после начала времени жизни нового объекта могут использоваться для работы с ним, но только если исходный объект прозрачно замещается новым объектом.
Если выполнены все следующие условия, объект 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(); // 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
| Если перечисленные выше условия не выполнены, допустимый указатель на новый объект всё ещё можно получить, применив барьер оптимизации указателей 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-квалификаторов).
Предоставление хранилища
В особом случае объекты могут создаваться в массивах unsignedchar или 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 provides storage
char* c = new (au.data) char(); // OK, ends lifetime of *p
char* d = new (au.data + 1) char();
return *c + *d; // OK
}
Доступ за пределами времени жизни
До начала времени жизни объекта, но после выделения хранилища, которое он будет занимать, либо после окончания времени жизни объекта и до повторного использования или освобождения занимаемого им хранилища поведение при следующих способах использования glvalue-выражения, обозначающего этот объект, не определено, если только объект не конструируется или не разрушается (в этом случае применяются отдельные правила):
- Преобразование lvalue в rvalue (например, вызов функции, принимающей значение).
- Доступ к нестатическому члену данных или вызов нестатической функции-члена.
- Привязка ссылки к подобъекту виртуального базового класса.
-
dynamic_castили выраженияtypeid.
Приведённые выше правила применяются также к указателям (привязка ссылки к виртуальной базе заменяется неявным преобразованием к указателю на виртуальную базу) с двумя дополнительными правилами:
-
static_castуказателя на хранилище без объекта разрешён только при приведении к void* (возможно, cv-квалифицированному). - Указатели на хранилище без объекта, приведённые к void* (возможно, cv-квалифицированному), можно
static_castтолько к указателям на char (возможно, cv-квалифицированному) или unsignedchar (возможно, cv-квалифицированному) либо наstd::byte(возможно, cv-квалифицированный)(начиная с C++17).
Во время конструирования и разрушения обычно разрешены вызовы нестатических функций-членов, доступ к нестатическим членам данных и использование typeid и dynamic_cast. Однако, поскольку время жизни ещё не началось (во время конструирования) или уже закончилось (во время разрушения), разрешены только определённые операции. Одно из ограничений описано в разделе вызовы виртуальных функций во время конструирования и разрушения.
Примечания
До принятия решения по вопросу CWG 2256 правила окончания времени жизни различались для неклассовых объектов (окончание длительности хранения) и объектов классов (обратный порядок конструирования):
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;
}
До принятия решения по 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: 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
}
Отчёты об ошибках стандарта
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение согласно опубликованной редакции | Корректное поведение |
|---|---|---|---|
| CWG 119 | C++98 | объект классового типа с нетривиальным конструктором мог начать время жизни только после завершения вызова конструктора | время жизни начиналось также при других видах инициализации |
| CWG 201 | C++98 | время жизни временного объекта в аргументе по умолчанию конструктора по умолчанию должно было закончиться после завершения инициализации массива | время жизни заканчивается до инициализации следующего элемента (также разрешает вопрос CWG 124) |
| CWG 274 | C++98 | lvalue, обозначающее объект вне времени жизни, можно было использовать как операнд static_cast, только если преобразование в конечном счёте выполнялось к cv-неквалифицированному char& или unsignedchar& | также разрешены cv-квалифицированные char& и unsignedchar& |
| CWG 597 | C++98 | следующее поведение считалось неопределённым: 1. указатель на объект вне времени жизни неявно преобразуется в указатель на невиртуальный базовый класс 2. lvalue, ссылающееся на объект вне времени жизни, привязывается к ссылке на невиртуальный базовый класс 3. lvalue, ссылающееся на объект вне времени жизни, используется как операнд static_cast (за некоторыми исключениями) | поведение определено |
| CWG 2012 | C++98 | время жизни ссылок было определено в соответствии с длительностью хранения, что требовало существования внешних ссылок до выполнения их инициализаторов | время жизни начинается при инициализации |
| CWG 2107 | C++98 | решение по вопросу CWG 124 не применялось к копирующим конструкторам | применено |
| CWG 2256 | C++98 | время жизни объектов с тривиальным деструктором не согласовывалось со временем жизни других объектов | правила согласованы |
| CWG 2470 | C++98 | несколько массивов могли предоставлять хранилище для одного и того же объекта | хранилище предоставляет только один массив |
| CWG 2489 | C++98 |
char[] не мог предоставлять хранилище, но объекты могли неявно создаваться в его хранилище | объекты не могут неявно создаваться в хранилище char[] |
| CWG 2523 | C++98 | если деструктор не вызывается из-за повторного использования хранилища, а программа зависит от побочных эффектов его вызова, поведение считалось неопределённым | в этом случае поведение определено |
| CWG 2721 | C++98 | точный момент повторного использования хранилища при размещающем new был неясен | уточнено |
| CWG 2849 | C++23 | объекты параметров функции считались временными объектами при продлении времени жизни временного объекта в цикле range-for | не считаются временными объектами |
| CWG 2854 | C++98 | объекты исключений считались временными объектами | они не являются временными объектами |
| CWG 2867 | C++17 | время жизни временных объектов, созданных в объявлениях структурных привязок, не продлевалось | продлено до конца объявления |
| P0137R1 | C++98 | создание объекта в массиве unsignedchar повторно использовало его хранилище | его хранилище не используется повторно |
| P0593R6 | C++98 | вызов псевдодеструктора не имел эффекта | он уничтожает объект |
| P1971R0 | C++98 | нестатический член данных константного или ссылочного типа препятствовал прозрачному замещению содержащего его объекта | ограничение снято |
| 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 для понятия время жизни |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/lifetime