Lifetime
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:
- se obtiene almacenamiento con la alineación y tamaño adecuados para su tipo, y
- su inicialización (si la hay) está completa (incluyendo default initialization mediante ningún constructor o trivial default constructor ), excepto que
-
- 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) |
|
(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
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.
|
(since C++17) |
|
(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):
- Acceder al objeto.
- Acceder a un miembro de datos no estático o una llamada a una función miembro no estática.
- Vincular una referencia a un subobjeto de clase base virtual.
-
dynamic_castotypeidexpresiones.
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:
-
static_castde un puntero a almacenamiento sin un objeto solo está permitido cuando se convierte a (posiblemente calificado con cv) void * . -
Los punteros a almacenamiento sin un objeto que fueron convertidos a (posiblemente calificado con cv)
void
*
solo pueden ser
static_casta 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
|