Lifetime

From cppreference.com
< cpplrm; | language

Every object and reference has a lifetime, which is a runtime property: for any object or reference, there is a point of execution of a program when its lifetime begins, and there is a moment when it ends.

  • For any object of class or aggregate types if it, or any of its subobjects, is initialized by anything other than the trivial default constructor, lifetime begins when initialization ends.
  • For any object of class types whose destructor is not trivial, lifetime ends when the execution of the destructor begins.
  • Lifetime of a member of a union begins when that member is made active
  • For all other objects (class objects initialized by a trivial default constructor, non-class objects, arrays of those, etc.), lifetime begins when the properly-aligned storage for the object is allocated and ends when the storage is deallocated or reused by another object.

Lifetime of an object is equal to or is nested within the lifetime of its storage, see storage duration.

Lifetime of a reference is exactly its storage duration.

(until C++14)

The lifetime of a reference begins when its initialization is complete and ends as if it were a scalar object.

(since C++14)

Note: the lifetime of the referred object may end before the end of the lifetime of the reference, which makes dangling references possible.

Lifetimes of member objects and base subobjects begin and end following class initialization order.

Temporary object lifetime

Temporary objects are created when a prvalue is materialized so that it can be used as a glvalue, which occurs (since C++17) in the following situations:

(until C++17)
(since C++17)

The materialization of a temporary object is generally delayed as long as possible in order to avoid creating unnecessary temporary object: see copy elision

(since C++17)

All temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created, and if multiple temporary objects were created, they are destroyed in the order opposite to the order of creation. This is true even if that evaluation ends in throwing an exception.

There are two exceptions from that:

  • The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.
  • The lifetime of a temporary object created when evaluating the default arguments of a default constructor used to initialize an element of an array ends before the next element of the array begins initialization.
(since C++11)

Storage reuse

A program is not required to call the destructor of an object to end its lifetime if the object is trivially-destructible or if the program does not rely on the side effects of the destructor. However, if a program ends the lifetime of an non-trivial object, it must ensure that a new object of the same type is constructed in-place (e.g. via placement new) before the destructor may be called implicitly, i.e. due to scope exit or exception for automatic objects, due to thread exit for thread-local objects, or due to program exit for static objects; otherwise the behavior is undefined.

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

It is undefined behavior to reuse storage that is or was occupied by a const complete object of static, thread-local, or automatic storage duration because such objects may be stored in read-only memory.

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
}

If a new object is created at the address that was occupied by another object, then all pointers, references, and the name of the original object will automatically refer to the new object and, once the lifetime of the new object begins, can be used to manipulate the new object, but only if the following conditions are satisfied:

  • the storage for the new object exactly overlays the storage location which the original object occupied
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers)
  • the type of the original object is not const-qualified
  • if the original object had class type, it does not contain any non-static data member whose type is const-qualified or a reference type
  • the original object was a most derived object of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).
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

If the conditions listed above are not met, a valid pointer to the new object may still be obtained by applying the pointer optimization barrier std::launder

Similarly, if an object is created in the storage of a class member or array element, the created object is only a subobject (member or element) of the original object's containing object if:

  • the lifetime of the containing object has begun and not ended
  • the storage for the new object exactly overlays the storage of the original object
  • the new object is of the same type as the original object (ignoring cv-qualification).

Otherwise (such as if the subobject contains a reference member or a const subobject), the name of the original subobject cannot be used to access the new object without std::launder:

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(*std::launder(&u.x.n) == 2); // OK
  assert(u.x.n == 2);                 // undefined: 'u.x' does not name the new subobject
}

As a special case, objects can be created in arrays of unsigned char or std::byte (in which case it is said that the array provides storage for the object) if

  • the lifetime of the array has begun and not ended
  • the storage for the new object fits entirely within the array
  • there is no smaller array object that satisfies these constraints.

If that portion of the array previously provided storage for another object, the lifetime of that object ends because its storage was reused, however the lifetime of the array itself does not end (its storage is not considered to have been reused)

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
}
(since C++17)

Access outside of lifetime

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, the following uses of the glvalue expression that identifies that object are undefined:

  1. Lvalue to rvalue conversion (e.g. function call to a function that takes a value).
  2. Access to a non-static data member or a call to a non-static member function.
  3. Binding a reference to a virtual base class subobject.
  4. dynamic_cast or typeid expressions.

The above rules apply to pointers as well (binding a reference to virtual base is replaced by implicit conversion to a pointer to virtual base), with two additional rules:

  1. static_cast of a pointer to storage without an object is only allowed when casting to (possibly cv-qualified) void*.
  2. Pointers to storage without an object that were cast to possibly cv-qualified void* can only be static_cast to pointers to possibly cv-qualified char, possibly cv-qualified unsigned char, or possibly cv-qualified std::byte.

During construction and destruction, other restrictions apply, see virtual function calls during construction and destruction.

Notes

The difference in the end of lifetime rules between non-class objects (end of storage duration) and class objects (reverse order of construction) matters in the following example:

struct A {
  int* p;
  ~A() { std::cout << *p; } // if n outlives a, prints 123
};
void f() {
  A a;
  int n = 123; // if n does not outlive a, this is optimized out (dead store)
  a.p = &n;
}

Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
CWG 2012 C++14 lifetime of references was specified to match storage duration, requiring that
extern references are alive before their initializers run
lifetime begins at initialization

See also