Espacios de nombres
Variantes
Acciones

Tiempo de vida

De cppreference.com
< cpp‎ | language

Cada objeto y referencia tiene un tiempo de vida, que es una propiedad en tiempo de ejecución: para cada objeto o referencia, hay un punto de la ejecución del programa donde comienza su tiempo de vida, y un momento en que termina.

  • Para cualquier objeto de clase o tipos agregados si este, o cualquiera de sus subobjetos, es inicializado por algo que no sea el constructor trivial por defecto, el tiempo de vida comienza cuando finaliza la inicialización.
  • Para cualquier objeto de tipo clase cuyo destructor no sea el predeterminado, el tiempo de vida finaliza cuando comienza la ejecución del destructor.
  • El tiempo de vida de un miembro de una union comienza cuando ese miembro se hace activo.
  • Para los demás objetos (objetos de clase incializados por el constructor por defecto, objetos no clase, matrices de estos, etc.), el tiempo de vida comienza cuando se reserva, correctamente alineado, el almacenamiento para el objeto, y termina cuando el almacenamiento se libera o se reutiliza para otro objeto.

El tiempo de vida de un objeto esta asociado con el tiempo de vida de su almacenamiento, ver duración del almacenamiento.

(hasta C++14) El tiempo de vida de una referencia es exactamente la duración de su almacenamiento.

(desde C++14) El tiempo de vida de una referencia comienza cuando se completa la inicialización y termina como si fuera un objeto escalar.

Nota: la vida útil de un objeto referenciado puede finalizar antes del final de la vida útil de la referencia, lo que posibilita referencias colgadas.

El tiempo de vida de los miembros de objeto y subojetos de base comienza y termina siguiendo el orden de inicialización de clase.

Contenido

[editar] Tiempo de vida de objetos temporales

Los objetos temporales se crean cuando un prvalue se materializa para que se pueda usar como un glvalue (desde C++17), que ocurre en las siguientes situaciones:

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


La materialización de un objeto temporal generalmente se retrasa el mayor tiempo posible para evitar crear objetos temporales innecesarios: ver copia elision.

(desde C++17)

Todos los objetos temporales se destruyen como último paso en la evaluación de la expresión completa que (léxicamente) contiene el punto donde se crearon, 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.

Hay dos excepciones:

  • El tiempo de vida de un objeto temporal se puede ampliar vinculándolo a una referencia constante lValue o a una referencia rvalue (desde C++11), para más detalles ver inicialización de referencia.
  • El tiempo de vida de un objeto temporal creado al evaluar los argumentos predeterminados de un constructor por defecto utilizado para inicializar un elemento de una matriz finaliza antes de que el siguiente elemento de la matriz comience la inicialización.
(desde C++11)

[editar] Reutilización de almacenamiento

Un programa no necesita llamar al destructor de clase para finalizar el tiempo de vida si el objeto tiene un destructor por defecto o si no afecta al programa los efectos del destructor. Sin embargo, si un programa finaliza la vida útil de un objeto no trivial, debe garantizar que un nuevo objeto del mismo tipo se construya in situ (por ejemplo, mediante el operador new) antes de que el destructor pueda invocarse implícitamente, es decir, debido a la salida del ámbito o una excepción para objetos automáticos, debido a la terminación de hilos para objetos locales al hilo o debido a la salida de programas para objetos estáticos; de lo contrario, el comportamiento es indeterminado.

class T {}; // trivial (destructor por defecto)
struct B {
    ~B() {} // no trivial (se define destructor)
};
void x() {
    long long n; // automático, trivial
    new (&n) double(3.14); // se reutiliza con un tipo diferente
} // correcto
void h() {
    B b; // automático, no destructible trivialmente
    b.~B(); // fin tiempo de vida (no necesario, puesto que no afecta a la ejecución)
    new (&b) T; // tipo erróneo: correcto hasta que se llama al destructor
} // se llama al destructor: comportamiento indeterminado.

El comportamiento es indeterminado para la reutilización de almacenamiento que está o estuvo ocupado por un objeto constante completo de almacenamiento estático, local al hilo o automático porque esos 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(); // fin del tiempo de vida de b
    new (const_cast<B*>(&b)) const B; // comportamiento indeterminado: 
                                      // se reutiliza el espacio de una constante
}

Si se crea un objeto nuevo en la dirección ocupada por otro objeto, entonces todos los punteros, referencias, y nombres del objeto original automáticamente se referirán al objeto nuevo y, una vez que comienza la vida del nuevo objeto, se pueden utilizar para manipular el nuevo objeto, pero solamente si se cumplen las siguientes condiciones:

  • el almacenamiento para el nuevo objeto coincide exactamente con el espacio que ocupaba el original.
  • el nuevo objeto es del mismo tipo que el original (ignorando los calificadores de alto nivel).
  • el tipo del objeto original no está calificado como constante.
  • si el objeto original tenía un tipo de clase, no contiene ningún miembro de datos no estáticos cuyo tipo es calificado constante o un tipo de referencia.
  • el objeto original era el objeto más derivado del tipo T y el nuevo objeto es el más derivado del tipo T (es decir, no son subobjetos de clase base).
struct C {
  int i;
  void f();
  const C& operator=( const C& );
};
const C& C::operator=( const C& other) {
  if ( this != &other ) {
    this->~C();          // termina tiempo de vida de *this
    new (this) C(other); // se crea 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 al nuevo objeto de tipo C

Si las condiciones anteriores no se cumplen, todavía se puede obtener un puntero válido al nuevo objeto aplicando la barrera de optimización del puntero std::launder.

Igualmente, si un objeto se crea en el almacenamiento de un miembro de clase o elemento de matriz, el objeto creado es solamente un subobjeto (miembro o elemento) del objeto que contiene el objeto original si:

  • el tiempo de vida del objeto que lo contiene comenzó y no ha finalizado.
  • el espacio de almacenamiento que ocupa el nuevo objeto es exactamente el mismo que el original.
  • el nuevo objeto es del mismo tipo que el original (ignorando los calificadores).

De lo contrario (si el subobjeto contiene un miembro de referencia o un subobjeto constante), el nombre del subobjeto original no se puede usar para acceder al nuevo objeto sin std::launder:

struct X { const int n; };
union U { X x; float f; };
void tong() {
  U u = { { 1 } };
  u.f = 5.f;                          // OK, crear nuevo subojeto de 'u'
  X *p = new (&u.x) X {2};            // OK, crear nuevo subobjeto de 'u'
  assert(p->n == 2);                  // OK
  assert(*std::launder(&u.x.n) == 2); // OK
  assert(u.x.n == 2);                 // indterminado: 'u.x' no nombra al nuevo subobjeto
}

Como caso especial, se pueden crear objetos en matrices de unsigned char or std::byte (en este caso se dice que la matriz provee almacenamiento para el objeto) si:

  • el tiempo de vida de la matriz comenzó y no ha finalizado
  • el espacio de la matriz es suficiente para albergar el nuevo objeto
  • no hay un objeto de la matriz que cumpla esas condiciones.

Si esa parte de la matriz proporcionó previamente almacenamiento para otro objeto, el tiempo de vida de ese objeto finaliza porque se reutilizó su almacenamiento; sin embargo, la vida útil de la matriz no termina (no se considera que su almacenamiento se haya 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 espacio
  char *c = new (au.data) char(); // OK, finaliza tiempo vida de *p
  char *d = new (au.data + 1) char();
  return *c + *d; // OK
}
(desde C++17)


[editar] 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 espacio que ocupará el objeto o, una vez que haya finalizado el tiempo de vida de un objeto y antes de que se reutilice o libere el almacenamiento, los siguientes usos de la expresión glvalue que identifica ese objeto es indeterminado:

  1. Conversión de lvalue to rvalue (por ejemplo: Llamada de función a una función que toma un valor)
  2. Acceso a un miembro de datos no estático o una llamada a una función miembro no estática.
  3. Enlace a una referencia a un subobjeto de la clase base virtual.
  4. expresiones dynamic_cast o typeid.

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

  1. static_cast de un puntero al almacenamiento sin un objeto solo se permite cuando se realiza un casting (posiblemente calificado) void*.
  2. Los punteros al almacenamiento sin un objeto al que se realizó cast probablemente calificado a void* solamente pueden realizar static_cast a punteros probablemente calificados a char, unsigned char o std::byte.

Durante la construcción y la destrucción, se aplican otras restricciones, ver llamadas a función virtual durante la construcción y la destrucción.

[editar] Notas

La diferencia entre las reglas de fin de tiempo de vida de objetos no de clase (fin de la duración del almacenamiento) y los objetos de clase (orden inverso de construcción) se puede ver en el siguiente ejemplo:

struct A {
  int* p;
  ~A() { std::cout << *p; } // si 'n' dura más tiempo que 'a', imprime 123
};
void f() {
  A a;
  int n = 123; // si 'n' no dura más que 'a', está optimizado (espacio muerto)
  a.p = &n;
}

[editar] See also

Documentación de C de tiempo de vida