Namespaces
Variants

Lifetime

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Каждый объект и ссылка имеют время жизни , которое является свойством времени выполнения: для любого объекта или ссылки существует момент выполнения программы, когда их время жизни начинается, и момент, когда оно заканчивается.

Время жизни объекта начинается, когда:

  • получено хранилище с надлежащим выравниванием и размером для его типа, и
  • его инициализация (если требуется) завершена (включая default initialization через отсутствие конструктора или trivial default constructor ), за исключением того, что
  • если объект является членом объединения или подобъектом такового, его время жизни начинается только если этот член объединения является инициализированным членом в объединении, или он становится активным,
  • если объект вложен в объект объединения, его время жизни может начаться если содержащий объект объединения присваивается или конструируется тривиальной специальной функцией-членом,
  • время жизни объекта массива также может начаться если он выделен с помощью std::allocator::allocate .

Некоторые операции неявно создают объекты типов с неявным временем жизни в заданной области памяти и начинают их время жизни. Если подобъект неявно созданного объекта не относится к типу с неявным временем жизни, его время жизни не начинается неявно.

Время жизни объекта заканчивается, когда:

  • если это неклассовый тип, объект уничтожается (возможно, через вызов псевдодеструктора), или
  • если это классовый тип, начинается вызов деструктора , или
  • память, которую занимает объект, освобождается или переиспользуется объектом, который не вложен в него.

Время жизни объекта равно или вложено во время жизни его хранилища, см. storage duration .

Время жизни ссылки начинается после завершения её инициализации и заканчивается так, как если бы она была скалярным объектом.

Примечание: время жизни объекта, на который ссылается ссылка, может завершиться до окончания времени жизни ссылки, что делает возможным возникновение висячих ссылок .

Время жизни нестатических элементов данных и подобъектов базовых классов начинается и заканчивается в соответствии с порядком инициализации класса .

Содержание

Время жизни временных объектов

Временные объекты создаются когда prvalue материализуется для использования в качестве glvalue, что происходит (начиная с C++17) в следующих ситуациях:

(начиная с C++11)
(since C++11)
(until C++17)

Материализация временного объекта обычно откладывается как можно дольше, чтобы избежать создания ненужных временных объектов: см. copy elision .

(since C++17)


Когда объект типа T передается в функцию или возвращается из потенциально вычисляемого вызова функции, если T является одним из следующих типов, реализациям разрешено создавать временные объекты для хранения параметра функции или результирующего объекта:

  • скалярный тип
(since C++26)
  • тип класса, удовлетворяющий всем следующим условиям:
    • T имеет по крайней мере один подходящий копирующий или перемещающий конструктор.
    • Каждый подходящий копирующий/перемещающий конструктор T является тривиальным.
    • Деструктор T является либо тривиальным, либо удаленным.

Временный объект конструируется из аргумента функции или возвращаемого значения соответственно, а параметр функции или возвращаемый объект инициализируется как если бы с использованием подходящего тривиального конструктора для копирования временного объекта (даже если этот конструктор недоступен или не был бы выбран разрешением перегрузки для выполнения копирования или перемещения объекта).

(until C++26)

Временные объекты создаются следующим образом:

  • Первый такой временный объект конструируется из аргумента функции или возвращаемого значения соответственно.
  • Каждый последующий временный объект инициализируется из предыдущего как если бы с помощью прямой инициализации если T является скалярным типом, в противном случае с использованием подходящего тривиального конструктора.
  • Параметр функции или возвращаемый объект инициализируется из последнего временного объекта как если бы прямой инициализацией если T является скалярным типом, в противном случае с использованием подходящего тривиального конструктора.

Во всех случаях подходящий конструктор используется даже если этот конструктор недоступен или не был бы выбран разрешением перегрузки для выполнения копирования или перемещения объекта.

(since C++26)

Эта возможность предоставлена для разрешения передачи объектов в функции и возврата из функций через регистры.

(since C++17)

Все временные объекты уничтожаются как последний шаг при вычислении полного выражения , которое (лексически) содержит точку, где они были созданы, и если было создано несколько временных объектов, они уничтожаются в порядке, обратном порядку создания. Это верно даже если вычисление завершается выбрасыванием исключения.

Из этого есть следующие исключения:

  • Время жизни временного объекта может быть продлено привязкой к ссылке, подробности смотрите в разделе инициализация ссылки .
  • Время жизни временного объекта, созданного при вычислении аргументов по умолчанию конструктора по умолчанию или копирования, используемого для инициализации или копирования элемента массива, завершается до начала инициализации следующего элемента массива.
  • Время жизни временного объекта, созданного в structured binding объявлении (введённого инициализатором для переменной с уникальным именем), продлевается до конца объявления structured binding.
(since C++17)
  • Время жизни временного объекта, созданного в range-initializer оператора range- for , который в противном случае был бы уничтожен в конце range-initializer , продлевается до конца тела цикла.
(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-выражения, идентифицирующего этот объект, является неопределенным, если только объект не конструируется или не уничтожается (применяется отдельный набор правил):

  1. Доступ к объекту.
  2. Доступ к нестатическому члену данных или вызов нестатической функции-члена.
  3. Привязка ссылки к подобъекту виртуального базового класса.
  4. dynamic_cast или typeid выражения.

Вышеуказанные правила применяются и к указателям (привязка ссылки к виртуальному базовому классу заменяется неявным преобразованием в указатель на виртуальный базовый класс) с двумя дополнительными правилами:

  1. static_cast указателя на область памяти без объекта разрешен только при приведении к (возможно cv-квалифицированному) void * .
  2. Указатели на область памяти без объекта, приведенные к возможно 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