resueltos que programacion orientada objetos objeto metodos libro herencia ejercicios ejemplos constructores codigo clases c++ exception destructor c++-faq object-lifetime

que - Destrucción de objetos en C++



programacion orientada a objetos c++ libro pdf (2)

¿Cuándo exactamente se destruyen los objetos en C ++, y qué significa eso? ¿Debo destruirlos manualmente, ya que no hay Recolector de basura? ¿Cómo entran en juego las excepciones?

(Nota: Esto debe ser una entrada a las preguntas frecuentes de C ++ de Stack Overflow . Si desea criticar la idea de proporcionar una pregunta frecuente en este formulario, entonces la publicación en meta que inició todo esto sería el lugar para hacerlo). esa pregunta se monitorea en la sala de chat de C ++ , donde la idea de las preguntas frecuentes comenzó en primer lugar, por lo que es muy probable que su respuesta sea leída por aquellos a quienes se les ocurrió la idea).


El destructor de un objeto se llama automáticamente cuando la vida útil del objeto finaliza y se destruye. Por lo general, no debes llamarlo manualmente.

Usaremos este objeto como un ejemplo:

class Test { public: Test() { std::cout << "Created " << this << "/n";} ~Test() { std::cout << "Destroyed " << this << "/n";} Test(Test const& rhs) { std::cout << "Copied " << this << "/n";} Test& operator=(Test const& rhs) { std::cout << "Assigned " << this << "/n";} };

Hay tres (cuatro en C ++ 11) distintos tipos de objetos en C ++ y el tipo de objeto define la vida útil de los objetos.

  • Objetos de duración de almacenamiento estático
  • Objetos de duración de almacenamiento automático
  • Objetos de duración de almacenamiento dinámico
  • (En C ++ 11) objetos de duración de almacenamiento de subprocesos

Objetos de duración de almacenamiento estático

Estas son las más simples y equivalentes a las variables globales. La vida útil de estos objetos es (normalmente) la duración de la aplicación. Estos son (usualmente) construidos antes de que se ingrese y destruya main (en el orden inverso al que se crearon) después de salir de main.

Test global; int main() { std::cout << "Main/n"; } > ./a.out Created 0x10fbb80b0 Main Destroyed 0x10fbb80b0

Nota 1: hay otros dos tipos de objetos de duración de almacenamiento estático.

variables miembro estáticas de una clase.

Estos son para todo sentido y propósito lo mismo que las variables globales en términos de vida útil.

variables estáticas dentro de una función.

Estos son objetos de duración de almacenamiento estático creados de forma perezosa. Se crean en el primer uso (en un feudo seguro para C ++ 11). Al igual que otros objetos de duración de almacenamiento estáticos, se destruyen cuando finaliza la aplicación.

Orden de construcción / destrucción

  • El orden de construcción dentro de una unidad de compilación está bien definido y es lo mismo que la declaración.
  • El orden de construcción entre las unidades de compilación no está definido.
  • El orden de destrucción es el inverso exacto del orden de construcción.

Objetos de duración de almacenamiento automático

Estos son los tipos de objetos más comunes y lo que debe usar el 99% del tiempo.

Estos son tres tipos principales de variables automáticas:

  • variables locales dentro de una función / bloque
  • variables miembro dentro de una clase / matriz.
  • variables temporales

Variables locales

Cuando se sale una función / bloque, todas las variables declaradas dentro de esa función / bloque serán destruidas (en el orden inverso de creación).

int main() { std::cout << "Main() START/n"; Test scope1; Test scope2; std::cout << "Main Variables Created/n"; { std::cout << "/nblock 1 Entered/n"; Test blockScope; std::cout << "block 1 about to leave/n"; } // blockScope is destrpyed here { std::cout << "/nblock 2 Entered/n"; Test blockScope; std::cout << "block 2 about to leave/n"; } // blockScope is destrpyed here std::cout << "/nMain() END/n"; }// All variables from main destroyed here. > ./a.out Main() START Created 0x7fff6488d938 Created 0x7fff6488d930 Main Variables Created block 1 Entered Created 0x7fff6488d928 block 1 about to leave Destroyed 0x7fff6488d928 block 2 Entered Created 0x7fff6488d918 block 2 about to leave Destroyed 0x7fff6488d918 Main() END Destroyed 0x7fff6488d930 Destroyed 0x7fff6488d938

variables miembro

La vida útil de una variable miembro está ligada al objeto que la posee. Cuando la vida útil de un propietario finaliza, la vida útil de todos sus miembros también finaliza. Por lo tanto, debe observar la vida útil de un propietario que obedece a las mismas reglas.

Nota: los miembros siempre se destruyen antes que el propietario en orden inverso a la creación.

  • Por lo tanto, para los miembros de la clase se crean en el orden de la declaración
    y destruido en el orden inverso de la declaración
  • Por lo tanto, para los miembros de la matriz, se crean por orden 0 -> arriba
    y destruido en el orden inverso superior -> 0

variables temporales

Estos son objetos que se crean como resultado de una expresión pero no están asignados a una variable. Las variables temporales se destruyen al igual que otras variables automáticas. Es solo que el final de su alcance es el final de la declaración en la que se crean (esto es usualmente el '';'').

std::string data("Text."); std::cout << (data + 1); // Here we create a temporary object. // Which is a std::string with ''1'' added to "Text." // This object is streamed to the output // Once the statement has finished it is destroyed. // So the temporary no longer exists after the '';''

Nota: Hay situaciones en las que se puede extender la vida de un temporal.
Pero esto no es relevante para esta simple discusión. Para cuando comprenda que este documento será de su propia naturaleza y que antes de que se extienda la vida de un temporal no es algo que desee hacer.

Objetos de duración de almacenamiento dinámico

Estos objetos tienen una vida útil dinámica y se crean con new y destruidos con una llamada a delete .

int main() { std::cout << "Main()/n"; Test* ptr = new Test(); delete ptr; std::cout << "Main Done/n"; } > ./a.out Main() Created 0x1083008e0 Destroyed 0x1083008e0 Main Done

Para los desarrolladores que provienen de los lenguajes recolectados, esto puede parecer extraño (administrando la vida útil de su objeto). Pero el problema no es tan malo como parece. En C ++ es inusual utilizar objetos dinámicamente asignados directamente. Tenemos objetos de administración para controlar su vida útil.

Lo más parecido a la mayoría de los demás lenguajes recopilados por GC es std::shared_ptr . Esto hará un seguimiento de la cantidad de usuarios de un objeto creado dinámicamente y cuando todos se hayan ido, se llamará a delete automáticamente (creo que esto es una mejor versión de un objeto Java normal).

int main() { std::cout << "Main Start/n"; std::shared_ptr<Test> smartPtr(new Test()); std::cout << "Main End/n"; } // smartPtr goes out of scope here. // As there are no other copies it will automatically call delete on the object // it is holding. > ./a.out Main Start Created 0x1083008e0 Main Ended Destroyed 0x1083008e0

Subprocesos objetos de duración de almacenamiento

Estos son nuevos en el lenguaje. Son muy parecidos a los objetos de duración de almacenamiento estáticos. Pero en lugar de vivir la misma vida que la aplicación que viven, siempre y cuando el hilo de ejecución que están asociados.


En el siguiente texto, distinguiré entre objetos delimitados , cuyo tiempo de destrucción está determinado estáticamente por su alcance (funciones, bloques, clases, expresiones) y objetos dinámicos , cuyo tiempo exacto de destrucción generalmente no se conoce hasta el tiempo de ejecución.

Mientras que la destrucción de la semántica de los objetos de clase está determinada por destructores, la destrucción de un objeto escalar siempre es no operativa. Específicamente, la destrucción de una variable de puntero no destruye la punta.

Objetos con alcance

objetos automáticos

Los objetos automáticos (comúnmente denominados "variables locales") se destruyen, en orden inverso a su definición, cuando el flujo de control deja el ámbito de su definición:

void some_function() { Foo a; Foo b; if (some_condition) { Foo y; Foo z; } <--- z and y are destructed here } <--- b and a are destructed here

Si se lanza una excepción durante la ejecución de una función, todos los objetos automáticos construidos previamente se destruyen antes de que la excepción se propague a la persona que llama. Este proceso se llama desenrollamiento de la pila . Durante el desenrollado de la pila, ninguna otra excepción puede dejar los destructores de los objetos automáticos previamente construidos anteriormente mencionados. De lo contrario, se llama a la función std::terminate .

Esto lleva a una de las pautas más importantes en C ++:

Los destruidores nunca deberían tirar.

objetos estáticos no locales

Los objetos estáticos definidos en el ámbito del espacio de nombres (comúnmente denominados "variables globales") y los miembros de datos estáticos se destruyen, en orden inverso a su definición, después de la ejecución de main :

struct X { static Foo x; // this is only a *declaration*, not a *definition* }; Foo a; Foo b; int main() { } <--- y, x, b and a are destructed here Foo X::x; // this is the respective definition Foo y;

Tenga en cuenta que el orden relativo de construcción (y destrucción) de los objetos estáticos definidos en diferentes unidades de traducción no está definido.

Si una excepción abandona el destructor de un objeto estático, se llama a la función std::terminate .

objetos estáticos locales

Los objetos estáticos definidos dentro de las funciones se construyen cuando (y si) el flujo de control pasa a través de su definición por primera vez. 1 Se destruyen en orden inverso después de la ejecución de main :

Foo& get_some_Foo() { static Foo x; return x; } Bar& get_some_Bar() { static Bar y; return y; } int main() { get_some_Bar().do_something(); // note that get_some_Bar is called *first* get_some_Foo().do_something(); } <--- x and y are destructed here // hence y is destructed *last*

Si una excepción abandona el destructor de un objeto estático, se llama a la función std::terminate .

1: Este es un modelo extremadamente simplificado. Los detalles de inicialización de objetos estáticos son mucho más complicados.

Subobjetos de clase base y subobjetos de miembros

Cuando el flujo de control abandona el cuerpo destructor de un objeto, sus subobjetos miembros (también conocidos como sus "miembros de datos") se destruyen en orden inverso a su definición. Después de eso, sus subobjetos de clase de base se destruyen en orden inverso de la base-especificador-lista:

class Foo : Bar, Baz { Quux x; Quux y; public: ~Foo() { } <--- y and x are destructed here, }; followed by the Baz and Bar base class subobjects

Si se lanza una excepción durante la construcción de uno de los subobjetos de Foo , todos sus subobjetos previamente construidos serán destruidos antes de que se propague la excepción. El destructor Foo , por otro lado, no se ejecutará, ya que el objeto Foo nunca se construyó por completo.

Tenga en cuenta que el cuerpo del destructor no es responsable de la destrucción de los miembros de los datos. Solo necesita escribir un destructor si un miembro de datos maneja un recurso que necesita liberarse cuando se destruye el objeto (como un archivo, un socket, una conexión de base de datos, un mutex o memoria de montón).

elementos de matriz

Los elementos de matriz se destruyen en orden descendente. Si se lanza una excepción durante la construcción del elemento n-ésimo, los elementos n-1 a 0 se destruyen antes de que se propague la excepción.

objetos temporales

Se construye un objeto temporal cuando se evalúa una expresión prvalue del tipo de clase. El ejemplo más destacado de una expresión prvalue es la llamada de una función que devuelve un objeto por valor, como T operator+(const T&, const T&) . En circunstancias normales, el objeto temporal se destruye cuando la expresión completa que contiene léxicamente el valor prvalue completamente evaluada:

__________________________ full-expression ___________ subexpression _______ subexpression some_function(a + " " + b); ^ both temporary objects are destructed here

La función anterior llama a some_function(a + " " + b) es una expresión completa porque no es parte de una expresión más grande (en cambio, es parte de una expresión-declaración). Por lo tanto, todos los objetos temporales que se construyen durante la evaluación de las subexpresiones serán destruidos en el punto y coma. Hay dos objetos temporales de este tipo: el primero se construye durante la primera adición y el segundo se construye durante la segunda adición. El segundo objeto temporal será destruido antes que el primero.

Si se lanza una excepción durante la segunda adición, el primer objeto temporal será destruido adecuadamente antes de propagar la excepción.

Si se inicializa una referencia local con una expresión de prvalue, la duración del objeto temporal se amplía al alcance de la referencia local, por lo que no obtendrá una referencia colgante:

{ const Foo& r = a + " " + b; ^ first temporary (a + " ") is destructed here // ... } <--- second temporary (a + " " + b) is destructed not until here

Si se evalúa una expresión prvalue de tipo no de clase, el resultado es un valor , no un objeto temporal. Sin embargo, se construirá un objeto temporal si el prvalue se usa para inicializar una referencia:

const int& r = i + j;

Arreglos y objetos dinámicos

En la siguiente sección, destruir X significa "primero destruir X y luego liberar la memoria subyacente". Del mismo modo, crear X significa "primero asignar suficiente memoria y luego construir X allí".

objetos dinámicos

Un objeto dinámico creado mediante p = new Foo se destruye mediante delete p . Si olvida delete p , tiene una fuga de recursos. Nunca intente hacer una de las siguientes acciones, ya que todas conducen a un comportamiento indefinido:

  • destruir un objeto dinámico a través de delete[] (tenga en cuenta los corchetes), free o de cualquier otro medio
  • destruir un objeto dinámico varias veces
  • acceder a un objeto dinámico después de que ha sido destruido

Si se lanza una excepción durante la construcción de un objeto dinámico, la memoria subyacente se libera antes de que se propague la excepción. (El destructor no se ejecutará antes de la liberación de la memoria, porque el objeto nunca se construyó por completo).

matrices dinámicas

Una matriz dinámica creada mediante p = new Foo[n] se destruye mediante delete[] p (tenga en cuenta los corchetes). Si olvida delete[] p , tiene una fuga de recursos. Nunca intente hacer una de las siguientes acciones, ya que todas conducen a un comportamiento indefinido:

  • destruir una matriz dinámica a través de delete , free o cualquier otro medio
  • destruir una matriz dinámica varias veces
  • acceder a una matriz dinámica después de que ha sido destruida

Si se lanza una excepción durante la construcción del elemento n-ésimo, los elementos n-1 a 0 se destruyen en orden descendente, se libera la memoria subyacente y se propaga la excepción.

(En general, debe preferir std::vector<Foo> sobre Foo* para matrices dinámicas. Hace que escribir código sea correcto y más sólido).

indicadores inteligentes de recuento de referencias

Un objeto dinámico administrado por varios objetos std::shared_ptr<Foo> se destruye durante la destrucción del último objeto std::shared_ptr<Foo> involucrado en el intercambio de ese objeto dinámico.

(En general, debe preferir std::shared_ptr<Foo> sobre Foo* para objetos compartidos. Hace que escribir código sea correcto y más sólido).