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

Cada object y reference tiene un lifetime , que es una propiedad de tiempo de ejecución: para cualquier object o reference, existe un punto de ejecución de un programa cuando su lifetime comienza, y hay un momento cuando termina.

La vida útil de un objeto comienza cuando:

  • si el objeto es un miembro de union o subobjeto del mismo, su tiempo de vida solo comienza si ese miembro de union es el miembro inicializado en la union, o se vuelve activo,
  • si el objeto está anidado en un objeto union, su tiempo de vida puede comenzar si el objeto union contenedor es asignado o construido por una función miembro especial trivial,
  • el tiempo de vida de un objeto array también puede comenzar si es asignado por std::allocator::allocate .

Algunas operaciones crean objetos implícitamente de tipos de duración implícita en una región de almacenamiento dada e inician su tiempo de vida. Si un subobjeto de un objeto creado implícitamente no es de un tipo de duración implícita, su tiempo de vida no comienza implícitamente.

La vida útil de un objeto termina cuando:

  • si es de un tipo no-clase, el objeto es destruido (quizás mediante una llamada a pseudo-destructor), o
  • si es de un tipo clase, la llamada al destructor comienza, o
  • el almacenamiento que el objeto ocupa es liberado, o es reutilizado por un objeto que no está anidado dentro de él.

La duración de un objeto es igual o está anidada dentro de la duración de su almacenamiento, consulte storage duration .

El tiempo de vida de una referencia comienza cuando su inicialización se completa y termina como si fuera un objeto escalar.

Nota: el tiempo de vida del objeto referenciado puede terminar antes del final del tiempo de vida de la referencia, lo que hace posible las dangling references .

Los tiempos de vida de los miembros de datos no estáticos y los subobjetos base comienzan y terminan siguiendo el orden de inicialización de la clase .

Contenidos

Duración de vida de objetos temporales

Los objetos temporales se crean cuando un prvalue es materializado para que pueda ser usado como glvalue, lo cual ocurre (since C++17) en las siguientes situaciones:

(desde C++11)
(desde C++11)
(hasta C++17)

La materialización de un objeto temporal generalmente se retrasa tanto como sea posible para evitar crear objetos temporales innecesarios: ver eliminación de copia .

(desde C++17)


Cuando un objeto de tipo T se pasa a o se devuelve desde una llamada a función potencialmente evaluada , si T es uno de los siguientes tipos, se permite a las implementaciones crear objetos temporales para contener el parámetro de función o el objeto resultado:

(desde C++26)

El objeto temporal se construye a partir del argumento de función o valor de retorno, respectivamente, y el parámetro de función u objeto de retorno se inicializa como si se usara el constructor trivial elegible para copiar el temporal (incluso si ese constructor es inaccesible o no sería seleccionado por la resolución de sobrecarga para realizar una copia o movimiento del objeto).

(hasta C++26)

Los objetos temporales se crean de la siguiente manera:

  • El primer objeto temporal se construye a partir del argumento de función o valor de retorno, respectivamente.
  • Cada objeto temporal sucesivo se inicializa a partir del anterior como si fuera por inicialización directa si T es un tipo escalar, de lo contrario usando un constructor trivial elegible.
  • El parámetro de función u objeto de retorno se inicializa a partir del temporal final como si fuera por inicialización directa si T es un tipo escalar, de lo contrario usando un constructor trivial elegible.

En todos los casos, se utiliza el constructor elegible incluso si ese constructor es inaccesible o no sería seleccionado por la resolución de sobrecarga para realizar una copia o movimiento del objeto.

(desde C++26)

Esta libertad se concede para permitir que los objetos se pasen a o se devuelvan desde funciones en registros.

(desde C++17)

Todos los objetos temporales se destruyen como el último paso en la evaluación de la expresión completa que (léxicamente) contiene el punto donde fueron creados, y si se crearon múltiples objetos temporales, se destruyen en el orden opuesto al orden de creación. Esto es cierto incluso si esa evaluación termina lanzando una excepción.

Existen las siguientes excepciones a eso:

  • La duración de un objeto temporal puede extenderse al vincularlo a una referencia, consulte inicialización de referencias para más detalles.
  • La duración de un objeto temporal creado al evaluar los argumentos por defecto de un constructor por defecto o de copia utilizado para inicializar o copiar un elemento de un array finaliza antes de que comience la inicialización del siguiente elemento del array.
  • La duración de un objeto temporal creado en una declaración de enlace estructurado (introducida por el inicializador para una variable con nombre único) se extiende hasta el final de la declaración de enlace estructurado.
(since C++17)
  • La duración de un objeto temporal creado en el inicializador-de-rango de una sentencia range- for que de otra manera sería destruido al final del inicializador-de-rango se extiende hasta el final del cuerpo del bucle.
(since C++23)

Reutilización de almacenamiento

Un programa no está obligado a llamar al destructor de un objeto para finalizar su tiempo de vida si el objeto es trivialmente-destructible (tenga cuidado de que el comportamiento correcto del programa puede depender del destructor). Sin embargo, si un programa finaliza explícitamente el tiempo de vida de un objeto no trivialmente destructible que es una variable, debe asegurarse de que se construya un nuevo objeto del mismo tipo in-situ (por ejemplo, mediante new de colocación) antes de que el destructor pueda ser llamado implícitamente, es decir, debido a la salida del ámbito o excepción para objetos automáticos , debido a la salida del hilo para objetos locales del hilo, (desde C++11) o debido a la salida del programa para objetos estáticos; de lo contrario, el comportamiento es indefinido.

class T {}; // trivial
struct B
{
    ~B() {} // no trivial
};
void x()
{
    long long n; // automática, trivial
    new (&n) double(3.14); // reutilizar con un tipo diferente está bien
} // correcto
void h()
{
    B b; // automática no trivialmente destructible
    b.~B(); // finalizar vida útil (no requerido, ya que no hay efectos secundarios)
    new (&b) T; // tipo incorrecto: correcto hasta que se llame al destructor
} // se llama al destructor: comportamiento indefinido

Es comportamiento indefinido reutilizar almacenamiento que está o estuvo ocupado por un objeto completo const de duración estática , thread-local, (since C++11) o automática porque dichos objetos pueden estar almacenados en memoria de solo lectura:

struct B
{
    B(); // no trivial
    ~B(); // no trivial
};
const B b; // estático constante
void h()
{
    b.~B(); // finalizar el tiempo de vida de b
    new (const_cast<B*>(&b)) const B; // comportamiento indefinido: intento de reutilización de una constante
}

Al evaluar una new expression , el almacenamiento se considera reutilizado después de que es devuelto por la función de asignación , pero antes de la evaluación del inicializador de la expresión new:

struct S
{
    int m;
};
void f()
{
    S x{1};
    new(&x) S(x.m); // comportamiento indefinido: el almacenamiento se reutiliza
}

Si un nuevo objeto es creado en la dirección que estaba ocupada por otro objeto, entonces todos los punteros, referencias y el nombre del objeto original automáticamente referirán al nuevo objeto y, una vez que comience el tiempo de vida del nuevo objeto, podrán usarse para manipular el nuevo objeto, pero solo si el objeto original es transparentemente reemplazable por el nuevo objeto.

Si se satisfacen todas las siguientes condiciones, el objeto x es transparentemente reemplazable por el objeto y :

  • El almacenamiento para y se superpone exactamente con la ubicación de almacenamiento que x ocupaba.
  • y es del mismo tipo que x (ignorando los calificadores cv de nivel superior).
  • x no es un objeto const completo.
  • Ni x ni y son un subobjeto de clase base , o un subobjeto miembro declarado con [[ no_unique_address ]] (desde C++20) .
  • Se satisface una de las siguientes condiciones:
  • x y y son ambos objetos completos.
  • x y y son subobjetos directos de los objetos ox y oy respectivamente, y ox es reemplazable transparentemente por oy .
struct C
{
    int i;
    void f();
    const C& operator=(const C&);
};
const C& C::operator=(const C& other)
{
    if (this != &other)
    {
        this->~C();          // la vida útil de *this termina
        new (this) C(other); // se crea un nuevo objeto de tipo C
        f();                 // bien definido
    }
    return *this;
}
C c1;
C c2;
c1 = c2; // bien definido
c1.f();  // bien definido; c1 se refiere a un nuevo objeto de tipo C

Si no se cumplen las condiciones enumeradas anteriormente, aún se puede obtener un puntero válido al nuevo objeto aplicando la barrera de optimización de puntero 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);
}
(desde C++17)

De manera similar, si un objeto se crea en el almacenamiento de un miembro de clase o un elemento de array, el objeto creado es solo un subobjeto (miembro o elemento) del objeto contenedor del objeto original si:

  • la vida útil del objeto contenedor ha comenzado y no ha terminado
  • el almacenamiento para el nuevo objeto cubre exactamente el almacenamiento del objeto original
  • el nuevo objeto es del mismo tipo que el objeto original (ignorando calificadores cv).

De lo contrario, el nombre del subobjeto original no puede utilizarse para acceder al nuevo objeto sin std::launder :

(desde C++17)

Proporcionando almacenamiento

Como caso especial, los objetos pueden ser creados en arrays de unsigned char o std::byte (desde C++17) (en cuyo caso se dice que el array proporciona almacenamiento para el objeto) si

  • la duración del array ha comenzado y no ha terminado
  • el almacenamiento para el nuevo objeto cabe completamente dentro del array
  • no hay ningún objeto array que satisfaga estas restricciones anidado dentro del array.

Si esa porción del array previamente proporcionó almacenamiento para otro objeto, la duración de ese objeto termina porque su almacenamiento fue reutilizado, sin embargo la duración del array en sí no termina (su almacenamiento no se considera haber sido reutilizado).

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 proporciona almacenamiento
    char *c = new (au.data) char(); // OK, finaliza el tiempo de vida de *p
    char *d = new (au.data + 1) char();
    return *c + *d; // OK
}

Acceso fuera del tiempo de vida

Antes de que comience el tiempo de vida de un objeto pero después de que se haya asignado el almacenamiento que ocupará el objeto, o después de que haya terminado el tiempo de vida de un objeto y antes de que se reutilice o libere el almacenamiento que ocupaba el objeto, los comportamientos de los siguientes usos de la expresión glvalue que identifica ese objeto no están definidos, a menos que el objeto esté siendo construido o destruido (se aplica un conjunto separado de reglas):

  1. Acceder al objeto.
  2. Acceder a un miembro de datos no estático o una llamada a una función miembro no estática.
  3. Vincular una referencia a un subobjeto de clase base virtual.
  4. dynamic_cast o typeid expresiones.

Las reglas anteriores también se aplican a los punteros (enlazar una referencia a una base virtual se reemplaza por la conversión implícita a un puntero a base virtual), con dos reglas adicionales:

  1. static_cast de un puntero a almacenamiento sin un objeto solo está permitido cuando se convierte a (posiblemente calificado con cv) void * .
  2. Los punteros a almacenamiento sin un objeto que fueron convertidos a (posiblemente calificado con cv) void * solo pueden ser static_cast a punteros a (posiblemente calificado con cv) char , o (posiblemente calificado con cv) unsigned char , o (posiblemente calificado con cv) std::byte (desde C++17) .

Durante la construcción y destrucción generalmente se permite llamar a funciones miembro no estáticas, acceder a datos miembro no estáticos, y usar typeid y dynamic_cast . Sin embargo, debido a que el tiempo de vida aún no ha comenzado (durante la construcción) o ya ha finalizado (durante la destrucción), solo se permiten operaciones específicas. Para una restricción, ver llamadas a funciones virtuales durante construcción y destrucción .

Notas

Hasta la resolución de CWG issue 2256 , las reglas de fin de vida son diferentes entre objetos no-clase (fin de la duración de almacenamiento) y objetos clase (orden inverso de construcción):

struct A
{
    int* p;
    ~A() { std::cout << *p; } // comportamiento indefinido desde CWG2256: n no sobrevive a a
                              // bien definido hasta CWG2256: imprime 123
};
void f()
{
    A a;
    int n = 123; // si n no sobreviviera a a, esto podría haberse optimizado (almacenamiento muerto)
    a.p = &n;
}

Hasta la resolución de RU007 , un miembro no estático de un tipo calificado como const o de un tipo de referencia impide que su objeto contenedor sea reemplazable de forma transparente, lo que hace que std::vector y std::deque sean difíciles de implementar:

struct X { const int n; };
union U { X x; float f; };
void tong()
{
    U u = { {1} };
    u.f = 5.f;                          // OK: crea un nuevo subobjeto de 'u'
    X *p = new (&u.x) X {2};            // OK: crea un nuevo subobjeto de 'u'
    assert(p->n == 2);                  // OK
    assert(u.x.n == 2);                 // indefinido hasta RU007:
                                        // 'u.x' no nombra el nuevo subobjeto
    assert(*std::launder(&u.x.n) == 2); // OK incluso hasta RU007
}

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 119 C++98 un objeto de tipo clase con un constructor no trivial solo podía
comenzar su vida útil cuando la llamada al constructor había finalizado
la vida útil también comienza
para otras inicializaciones
CWG 201 C++98 se requería que la vida útil de un objeto temporal en un argumento por defecto
de un constructor por defecto finalizara cuando la inicialización
del array se completaba
la vida útil finaliza antes
de inicializar el siguiente
elemento (también resuelve
CWG issue 124 )
CWG 274 C++98 un lvalue que designa un objeto fuera de su vida útil podía
usarse como operando de static_cast solo si la conversión
era finalmente a cv-unqualified char & o unsigned char &
cv-qualified char &
y unsigned char &
también permitidos
CWG 597 C++98 los siguientes comportamientos eran indefinidos:
1. un puntero a un objeto fuera de su vida útil se convierte implícitamente
a un puntero a una clase base no virtual
2. un lvalue que referencia a un objeto fuera de su vida útil
se vincula a una referencia a una clase base no virtual
3. un lvalue que referencia a un objeto fuera de su vida útil se usa
como operando de un static_cast (con algunas excepciones)
se define correctamente
CWG 2012 C++98 la vida útil de las referencias se especificaba para coincidir con la duración de almacenamiento,
requiriendo que las referencias externas estuvieran vivas antes de ejecutar sus inicializadores
la vida útil comienza
en la inicialización
CWG 2107 C++98 la resolución de CWG issue 124 no se aplicaba a constructores de copia aplicada
CWG 2256 C++98 la vida útil de objetos trivialmente destructibles era inconsistente con otros objetos se hace consistente
CWG 2470 C++98 más de un array podía proporcionar almacenamiento para el mismo objeto solo uno proporciona
CWG 2489 C++98 char [ ] no puede proporcionar almacenamiento, pero los objetos
podían crearse implícitamente dentro de su almacenamiento
los objetos no pueden
crearse implícitamente dentro
del almacenamiento de char [ ]
CWG 2527 C++98 si un destructor no se invoca debido a la reutilización de almacenamiento y el
programa depende de sus efectos secundarios, el comportamiento era indefinido
el comportamiento está bien
definido en este caso
CWG 2721 C++98 el momento exacto de reutilización de almacenamiento no estaba claro para placement new se aclara
CWG 2849 C++23 los objetos parámetro de función se consideraban como objetos temporales
para la extensión de vida útil de objetos temporales en bucles range- for
no se consideran como
objetos temporales
CWG 2854 C++98 los objetos de excepción eran objetos temporales no son
objetos temporales
CWG 2867 C++17 la vida útil de objetos temporales creados en
declaraciones de enlace estructurado no se extendía
extendida hasta el final
de la declaración
P0137R1 C++98 crear un objeto en un array de unsigned char reutilizaba su almacenamiento su almacenamiento no se reutiliza
P0593R6 C++98 una llamada a pseudo-destructor no tenía efectos destruye el objeto
P1971R0 C++98 un miembro de datos no estático de un tipo calificado const o un tipo referencia
impedía que su objeto contenedor fuera transparentemente reemplazable
restricción eliminada
P2103R0 C++98 la capacidad de reemplazo transparente no requería mantener la estructura original requiere

Referencias

  • Estándar C++23 (ISO/IEC 14882:2024):
  • 6.7.3 Duración de objetos [basic.life]
  • 11.9.5 Construcción y destrucción [class.cdtor]
  • Estándar C++20 (ISO/IEC 14882:2020):
  • 6.7.3 Duración de objetos [basic.life]
  • 11.10.4 Construcción y destrucción [class.cdtor]
  • Estándar C++17 (ISO/IEC 14882:2017):
  • 6.8 Duración de vida de objetos [basic.life]
  • 15.7 Construcción y destrucción [class.cdtor]
  • Estándar C++14 (ISO/IEC 14882:2014):
  • 3 Vida útil de objetos [basic.life]
  • 12.7 Construcción y destrucción [class.cdtor]
  • Estándar C++11 (ISO/IEC 14882:2011):
  • 3.8 Duración de objetos [basic.life]
  • 12.7 Construcción y destrucción [class.cdtor]
  • Estándar C++03 (ISO/IEC 14882:2003):
  • 3.8 Duración de objetos [basic.life]
  • 12.7 Construcción y destrucción [class.cdtor]
  • Estándar C++98 (ISO/IEC 14882:1998):
  • 3.8 Duración de objetos [basic.life]
  • 12.7 Construcción y destrucción [class.cdtor]

Véase también

Documentación de C para Lifetime