Жизненный цикл
У каждого объекта и ссылки есть жизненный цикл, являющийся свойством во время выполнения: для любого объекта или ссылки существует момент выполнения программы, когда его жизненный цикл начинается, и момент, когда он заканчивается.
Жизненный цикл объекта начинается, когда:
- получен хранилище с соответствующим выравниванием и размером для его типа, и
- завершено его инициализирование (если таковое имеется) (включая инициализацию по умолчанию без конструктора или тривиального конструктора по умолчанию), за исключением случаев, когда
- если объект является членом объединения или подобъектом, его жизненный цикл начинается только в том случае, если этот член объединения является инициализированным членом объединения или он активирован,
- если объект вложен в объект объединения, его жизненный цикл может начаться, если содержащий объект объединения присваивается или конструируется тривиальной специальной членом функции,
- жизненный цикл объекта массива также может начаться, если он выделен с помощью
std::allocator::allocate.
Некоторые операции неявно создают объекты типов с неявным жизненным циклом в заданной области хранилища и запускают их жизненный цикл. Если подобъект неявно созданного объекта не является типом с неявным жизненным циклом, его жизненный цикл не начинается неявно.
Жизненный цикл объекта заканчивается, когда:
- если он имеет тип, не являющийся классом, объект уничтожается (возможно, с помощью вызова псевдодеструктора), или
- если он имеет тип класса, вызывается деструктор, или
- хранилище, занимаемое объектом, освобождается или повторно используется объектом, который не вложен в него.
Жизненный цикл объекта равен или вложен в жизненный цикл его хранилища, см. длительность хранения.
Жизненный цикл ссылки начинается, когда завершена ее инициализация, и заканчивается как если бы это был скалярный объект.
Примечание: жизненный цикл объекта, на который ссылается ссылка, может закончиться до окончания жизненного цикла ссылки, что делает возможными висячие ссылки.
Жизненные циклы нестатических членов данных и под-объектов базовых классов начинаются и заканчиваются в соответствии с порядком инициализации класса.
Жизненный цикл временных объектов
Временные объекты создаются при материализации правого значения, чтобы его можно было использовать как левое значение, что происходит(с 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-квалификаторов).
| В противном случае, имя исходного под-объекта нельзя использовать для доступа к новому объекту без | (с C++17) |
Providing storage
В качестве специального случая, объекты могут создаваться в массивах unsigned char или байтов(с 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
}Access outside of lifetime
До начала срока жизни объекта, но после выделения памяти, которую будет занимать объект, или после окончания срока жизни объекта и до повторного использования или освобождения памяти, которую занимал объект, поведение следующих применений выражения glvalue, идентифицирующего этот объект, является неопределённым, если объект не находится в процессе построения или разрушения (применяются отдельные правила):
- Преобразование lvalue в rvalue (например, вызов функции, принимающей значение).
- Доступ к нестатическому члену данных или вызов нестатического метода.
- Связывание ссылки на под-объект виртуального базового класса.
-
dynamic_castилиtypeidвыражения.
Вышеупомянутые правила применяются и к указателям (связывание ссылки на виртуальный базовый класс заменяется неявным преобразованием в указатель на виртуальный базовый класс), с двумя дополнительными правилами:
-
static_castуказателя на память без объекта разрешается только при преобразовании к (возможно cv-квалифицированному) void*. - Указатели на память без объекта, которые были преобразованы к возможно cv-квалифицированному void*, могут быть только
static_castк указателям на возможно cv-квалифицированный char, или возможно cv-квалифицированный unsigned char, или возможно cv-квалифицированный байт(с C++17).
Во время построения и разрушения обычно разрешается вызывать нестатические методы, получать доступ к нестатическим членам данных и использовать typeid и dynamic_cast. Однако, поскольку срок жизни ещё не начался (во время построения) или уже закончился (во время разрушения), разрешены только определённые операции. Одно ограничение см. в вызовах виртуальных функций во время построения и разрушения.
Notes
До разрешения проблемы 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
}Defect reports
Следующие дефекты, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применено к | Поведение, как опубликовано | Правильное поведение |
|---|---|---|---|
| CWG 119 | C++98 | объект типа класса с нетривиальным конструктором может начать свой срок жизни только после завершения вызова конструктора | срок жизни также начал действовать для других инициализаций |
| CWG 201 | C++98 | срок жизни временного объекта в аргументе по умолчанию конструктора по умолчанию должен был закончиться после завершения инициализации массива | срок жизни заканчивается до инициализации следующего элемента (также разрешает CWG 124) |
| CWG 274 | C++98 | lvalue, обозначающее объект вне срока жизни, могло использоваться в качестве операнда static_cast только в том случае, если преобразование в конечном итоге осуществлялось к cv-неквалифицированному char& или unsigned char& | cv-квалифицированные char& и unsigned char& также разрешены |
| 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 2527 | C++98 | если деструктор не вызывается из-за повторного использования памяти, а программа зависит от его побочных эффектов, поведение было неопределённым | поведение определено в этом случае |
| CWG 2721 | C++98 | точное время повторного использования памяти для размещенного нового объекта было неясным | сделано ясным |
| P0137R1 | C++98 | создание объекта в массиве unsigned char повторно использовало его память | его память не используется повторно |
| P0593R6 | C++98 | псевдовызов деструктора не имел эффектов | он уничтожает объект |
| P1971R0 | C++98 | нестатический член константного типа или типа ссылки препятствовал прозрачной замене содержащего его объекта | ограничение удалено |
| P2103R0 | C++98 | прозрачная заменяемость не требовала сохранения исходной структуры | требует |
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2023):
- 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/w/cpp/language/lifetime