Duración del objeto

format_list_bulleted Contenido keyboard_arrow_down
ImprimirCitar
En programación, tiempo entre la creación y destrucción de un objeto

En la programación orientada a objetos (OOP), la vida útil del objeto (o ciclo de vida) de un objeto es el tiempo entre la creación de un objeto y su destrucción. Las reglas para la vida útil de los objetos varían significativamente entre idiomas, en algunos casos entre implementaciones de un idioma determinado, y la vida útil de un objeto en particular puede variar de una ejecución del programa a otra.

En algunos casos, la duración del objeto coincide con la duración de la variable de una variable con ese objeto como valor (tanto para las variables estáticas como para las variables automáticas), pero en general, la duración del objeto no está ligada a la duración de ninguna variable. En muchos casos, y de forma predeterminada en muchos lenguajes orientados a objetos, particularmente aquellos que usan recolección de basura (GC), los objetos se asignan en el montón y la vida útil del objeto no está determinada por la vida útil de una variable dada: el valor de una variable mantener un objeto en realidad corresponde a una referencia al objeto, no al objeto en sí, y la destrucción de la variable solo destruye la referencia, no el objeto subyacente.

Resumen

Si bien la idea básica de la vida útil de un objeto es simple (un objeto se crea, se usa y luego se destruye), los detalles varían sustancialmente entre idiomas y dentro de las implementaciones de un idioma determinado, y están íntimamente ligados a cómo se implementa la administración de memoria. Además, se trazan muchas distinciones finas entre los pasos y entre los conceptos de nivel de lenguaje y los conceptos de nivel de implementación. La terminología es relativamente estándar, pero los pasos que corresponden a un término dado varían significativamente entre idiomas.

Los términos generalmente vienen en pares de antónimos, uno para un concepto de creación, otro para el concepto de destrucción correspondiente, como inicializar/finalizar o constructor/destructor. La creación/destrucción par también se conoce como iniciación/terminación, entre otros términos. Los términos asignación y desasignación o liberación también se utilizan, por analogía con la gestión de memoria, aunque la creación y destrucción de objetos pueden implicar mucho más que simplemente memoria. la asignación y la desasignación, y la asignación/desasignación se consideran más apropiadamente pasos en la creación y destrucción, respectivamente.

Determinismo

Una distinción importante es si la vida útil de un objeto es determinista o no determinista. Esto varía según el idioma, y dentro del idioma varía con la asignación de memoria de un objeto; la duración del objeto puede ser distinta de la duración de la variable.

Los objetos con asignación de memoria estática, en particular los objetos almacenados en variables estáticas y los módulos de clases (si las clases o los módulos son en sí mismos objetos y están asignados estáticamente), tienen un sutil no determinismo en muchos lenguajes: mientras que su vida parece coincidir con el tiempo de ejecución del programa, el orden de creación y destrucción (qué objeto estático se crea primero, qué segundo, etc.) generalmente no es determinista.

Para objetos con asignación de memoria automática o asignación de memoria dinámica, la creación de objetos generalmente ocurre de manera determinista, ya sea explícitamente cuando un objeto se crea explícitamente (como a través de new en C++ o Java), o implícitamente en el inicio de la vida útil de la variable, particularmente cuando se ingresa el alcance de una variable automática, como en la declaración. Sin embargo, la destrucción de objetos varía: en algunos lenguajes, especialmente C ++, los objetos automáticos y dinámicos se destruyen en momentos deterministas, como la salida del alcance, la destrucción explícita (mediante la gestión manual de la memoria) o el recuento de referencias que llega a cero; mientras que en otros lenguajes, como C#, Java y Python, estos objetos se destruyen en momentos no deterministas, según el recolector de elementos no utilizados, y la resurrección del objeto puede ocurrir durante la destrucción, extendiendo la vida útil.

En los lenguajes de recolección de basura, los objetos generalmente se asignan dinámicamente (en el montón) incluso si inicialmente están vinculados a una variable automática, a diferencia de las variables automáticas con valores primitivos, que generalmente se asignan automáticamente (en la pila o en un registro).). Esto permite que el objeto sea devuelto desde una función ("escape") sin ser destruido. Sin embargo, en algunos casos es posible una optimización del compilador, es decir, realizar un análisis de escape y probar que el escape no es posible y, por lo tanto, el objeto puede ubicarse en la pila; esto es significativo en Java. En este caso, la destrucción del objeto ocurrirá rápidamente, posiblemente incluso durante la vida útil de la variable (antes del final de su alcance), si es inalcanzable.

Un caso complejo es el uso de un conjunto de objetos, donde los objetos pueden crearse con anticipación o reutilizarse y, por lo tanto, la creación y destrucción aparentes pueden no corresponder a la creación y destrucción reales de un objeto, solo la (re) inicialización para la creación. y finalización para su destrucción. En este caso, tanto la creación como la destrucción pueden ser no deterministas.

Pasos

La creación de objetos se puede dividir en dos operaciones: asignación de memoria e inicialización, donde la inicialización incluye la asignación de valores a campos de objetos y posiblemente la ejecución de otro código arbitrario. Estos son conceptos a nivel de implementación, más o menos análogos a la distinción entre declaración y definición de una variable, aunque estas últimas son distinciones a nivel de lenguaje. Para un objeto que está vinculado a una variable, la declaración se puede compilar para la asignación de memoria (reservando espacio para el objeto) y la definición para la inicialización (asignando valores), pero las declaraciones también pueden ser solo para uso del compilador (como resolución de nombres), no corresponde directamente al código compilado.

De manera análoga, la destrucción de objetos se puede dividir en dos operaciones, en el orden opuesto: finalización y memoria desasignación. Estos no tienen conceptos de nivel de lenguaje análogos para variables: el tiempo de vida de la variable finaliza implícitamente (para variables automáticas, en el desenredado de la pila; para variables estáticas, en la finalización del programa), y en este momento (o más tarde, según la implementación) la memoria se desasigna, pero no se finaliza en general. Sin embargo, cuando la vida útil de un objeto está vinculada a la vida útil de una variable, el final de la vida útil de la variable provoca la finalización del objeto; este es un paradigma estándar en C++.

Juntos, estos producen cuatro pasos a nivel de implementación:

asignación, inicialización, finalización, distribución

Estos pasos pueden ser realizados automáticamente por el tiempo de ejecución del idioma, el intérprete o la máquina virtual, o pueden ser especificados manualmente por el programador en una subrutina, concretamente a través de métodos; la frecuencia de esto varía significativamente entre los pasos y los idiomas. La inicialización es muy comúnmente especificada por el programador en lenguajes basados en clases, mientras que en lenguajes estrictamente basados en prototipos, la inicialización se realiza automáticamente mediante copia. La finalización también es muy común en lenguajes con destrucción determinista, especialmente C++, pero mucho menos común en lenguajes de recolección de elementos no utilizados. La asignación se especifica con menos frecuencia y, por lo general, no se puede especificar la desasignación.

Estado durante la creación y destrucción

Una sutileza importante es el estado de un objeto durante la creación o la destrucción, y el manejo de los casos en los que se producen errores o surgen excepciones, como si la creación o la destrucción fallan. Estrictamente hablando, la vida útil de un objeto comienza cuando se completa la asignación y finaliza cuando comienza la desasignación. Por lo tanto, durante la inicialización y la finalización, un objeto está vivo, pero es posible que no esté en un estado coherente (garantizar las invariantes de clase es una parte clave de la inicialización) y el período desde que se completa la inicialización hasta que comienza la finalización es cuando el objeto está vivo y se espera que lo haga. estar en un estado consistente.

Si la creación o la destrucción fallan, el informe de errores (a menudo generando una excepción) puede ser complicado: el objeto o los objetos relacionados pueden estar en un estado inconsistente y, en el caso de la destrucción, que generalmente ocurre implícitamente y, por lo tanto, en un entorno no especificado: puede ser difícil manejar los errores. El problema opuesto (excepciones entrantes, no excepciones salientes) es si la creación o la destrucción deben comportarse de manera diferente si ocurren durante el manejo de excepciones, cuando se desea un comportamiento diferente.

Otra sutileza es cuando ocurre la creación y destrucción de variables estáticas, cuya vida útil coincide con el tiempo de ejecución del programa: ¿la creación y destrucción ocurren durante la ejecución normal del programa? o en fases especiales antes y después de la ejecución regular, y cómo se destruyen los objetos al finalizar el programa, cuando el programa puede no estar en un estado habitual o consistente. Esto es particularmente un problema para los lenguajes recolectados como basura, ya que pueden tener mucha basura al finalizar el programa.

Programación basada en clases

En la programación basada en clases, la creación de objetos también se conoce como instanciación (crear una instancia de una clase), y creación y destrucción puede controlarse mediante métodos conocidos como constructor y destructor, o inicializador y finalizador. La creación y la destrucción son por lo tanto, también conocido como construcción y destrucción, y cuando se llama a estos métodos, se dice que un objeto está construido o destruido (no "destruido") – respectivamente, inicializado o finalizado cuando se llama a esos métodos.

La relación entre estos métodos puede ser complicada y un lenguaje puede tener tanto constructores como inicializadores (como Python), o destructores y finalizadores (como C++/CLI), o los términos "destructor" y "finalizador" puede hacer referencia a la construcción a nivel de lenguaje frente a la implementación (como en C# frente a CLI).

Una distinción clave es que los constructores son métodos de clase, ya que no hay ningún objeto (instancia de clase) disponible hasta que se crea el objeto, pero los otros métodos (destructores, inicializadores y finalizadores) son métodos de instancia, ya que un objeto ha sido creado. Además, los constructores y los inicializadores pueden tomar argumentos, mientras que los destructores y los finalizadores generalmente no, ya que generalmente se les llama implícitamente.

En el uso común, un constructor es un método directamente llamado explícitamente por el código de usuario para crear un objeto, mientras que "destructor" es la subrutina llamada (generalmente implícitamente, pero a veces explícitamente) en la destrucción de objetos en lenguajes con vidas de objetos deterministas (el arquetipo es C++) y "finalizador" es la subrutina llamada implícitamente por el recolector de elementos no utilizados en la destrucción de objetos en lenguajes con duración de objeto no determinista: el arquetipo es Java.

Los pasos durante la finalización varían significativamente dependiendo de la gestión de la memoria: en la gestión manual de la memoria (como en C++, o el recuento manual de referencias), el programador debe destruir las referencias explícitamente (referencias borradas, recuentos de referencia reducidos); en el conteo automático de referencias, esto también ocurre durante la finalización, pero está automatizado (como en Python, cuando ocurre después de que se haya llamado a los finalizadores especificados por el programador); y al rastrear la recolección de basura esto no es necesario. Por lo tanto, en el conteo automático de referencias, los finalizadores especificados por el programador a menudo son cortos o están ausentes, pero aún se puede hacer un trabajo significativo, mientras que en el seguimiento de los recolectores de basura, la finalización a menudo es innecesaria.

Gestión de recursos

En lenguajes donde los objetos tienen tiempos de vida deterministas, el tiempo de vida del objeto se puede usar para llevar a cuestas la administración de recursos: esto se denomina expresión idiomática de adquisición de recursos es inicialización (RAII): los recursos se adquieren durante la inicialización y se liberan durante la finalización. En los lenguajes en los que los objetos tienen una vida útil no determinista, en particular debido a la recolección de elementos no utilizados, la gestión de la memoria generalmente se mantiene separada de la gestión de otros recursos.

Creación de objetos

En un caso típico, el proceso es el siguiente:

  • calcular el tamaño de un objeto – el tamaño es mayormente el mismo que el de la clase, pero puede variar. Cuando el objeto en cuestión no se deriva de una clase, sino de un prototipo en su lugar, el tamaño de un objeto es generalmente el de la estructura de datos interna (un hash por ejemplo) que tiene sus ranuras.
  • asignación – asignación de espacio de memoria con el tamaño de un objeto más el crecimiento más adelante, si es posible saber de antemano
  • Métodos de unión – esto generalmente se deja a la clase del objeto, o se resuelve en el tiempo de envío, pero sin embargo es posible que algunos modelos de objetos atan métodos en el momento de la creación.
  • llamando un código inicial (denominado, constructor) de superclase
  • llamando un código inicial de clase que se crea

Esas tareas se pueden completar a la vez, pero a veces se dejan sin terminar y el orden de las tareas puede variar y causar varios comportamientos extraños. Por ejemplo, en la herencia múltiple, qué código de inicialización debe llamarse primero es una pregunta difícil de responder. Sin embargo, los constructores de superclases deben llamarse antes que los constructores de subclases.

Es un problema complejo crear cada objeto como un elemento de una matriz. Algunos lenguajes (por ejemplo, C++) dejan esto en manos de los programadores.

Manejar excepciones en medio de la creación de un objeto es particularmente problemático porque, por lo general, la implementación de generar excepciones se basa en estados de objetos válidos. Por ejemplo, no hay forma de asignar un nuevo espacio para un objeto de excepción cuando la asignación de un objeto falló antes debido a la falta de espacio libre en la memoria. Debido a esto, las implementaciones de los lenguajes OO deben proporcionar mecanismos para permitir generar excepciones incluso cuando hay escasez de recursos, y los programadores o el sistema de tipos deben garantizar que su código sea seguro para excepciones. Es más probable que la propagación de una excepción libere recursos que los asigne. Pero en la programación orientada a objetos, la construcción de objetos puede fallar, porque la construcción de un objeto debería establecer las invariantes de clase, que a menudo no son válidas para todas las combinaciones de argumentos del constructor. Por lo tanto, los constructores pueden generar excepciones.

El patrón de fábrica abstracto es una forma de desacoplar una implementación particular de un objeto del código para la creación de dicho objeto.

Métodos de creación

La forma de crear objetos varía según el idioma. En algunos lenguajes basados en clases, un método especial conocido como constructor es responsable de validar el estado de un objeto. Al igual que los métodos ordinarios, los constructores se pueden sobrecargar para que se pueda crear un objeto con diferentes atributos especificados. Además, el constructor es el único lugar para establecer el estado de los objetos inmutables. Un constructor de copias es un constructor que toma un parámetro (único) de un objeto existente del mismo tipo que la clase del constructor y devuelve una copia del objeto enviado como parámetro.

Otros lenguajes de programación, como Objective-C, tienen métodos de clase, que pueden incluir métodos de tipo constructor, pero no se limitan a simplemente crear instancias de objetos.

C++ y Java han sido criticados por no proporcionar constructores con nombre; un constructor siempre debe tener el mismo nombre que la clase. Esto puede ser problemático si el programador quiere proporcionar dos constructores con los mismos tipos de argumentos, por ejemplo, para crear un objeto de punto a partir de las coordenadas cartesianas o de las coordenadas polares, las cuales estarían representadas por dos números de punto flotante. Objective-C puede sortear este problema, ya que el programador puede crear una clase Point, con métodos de inicialización, por ejemplo, +newPointWithX:andY:, y +newPointWithR:andTheta:. En C++, se puede hacer algo similar usando funciones miembro estáticas.

Un constructor también puede referirse a una función que se usa para crear un valor de una unión etiquetada, particularmente en lenguajes funcionales.

Destrucción de objetos

Por lo general, después de que se usa un objeto, se elimina de la memoria para dejar espacio para que otros programas u objetos tomen su lugar. Sin embargo, si hay suficiente memoria o un programa tiene un tiempo de ejecución corto, es posible que no se destruya el objeto, simplemente se desasigna la memoria al finalizar el proceso. En algunos casos, la destrucción de objetos consiste simplemente en desasignar la memoria, particularmente en lenguajes de recolección de elementos no utilizados, o si el "objeto" es en realidad una estructura de datos simple y antigua. En otros casos, se realiza algún trabajo antes de la desasignación, en particular, la destrucción de objetos miembro (en la gestión de memoria manual) o la eliminación de referencias del objeto a otros objetos para disminuir los recuentos de referencias (en el recuento de referencias). Esto puede ser automático, o se puede invocar un método de destrucción especial en el objeto.

En los lenguajes basados en clases con duración de objeto determinista, en particular C++, un destructor es un método llamado cuando se elimina una instancia de una clase, antes de que se desasigne la memoria. En C++, los destructores se diferencian de los constructores en varios aspectos: no se pueden sobrecargar, no deben tener argumentos, no es necesario que mantengan invariantes de clase y pueden provocar la finalización del programa si arrojan excepciones.

En los lenguajes de recolección de basura, los objetos pueden destruirse cuando el código en ejecución ya no puede alcanzarlos. En los lenguajes GCed basados en clases, el análogo de los destructores son los finalizadores, a los que se llama antes de que un objeto se recolecte como basura. Éstos difieren en que se ejecutan en un momento impredecible y en un orden impredecible, ya que la recolección de elementos no utilizados es impredecible y son significativamente menos utilizados y menos complejos que los destructores de C++. Ejemplos de tales lenguajes incluyen Java, Python y Ruby.

Destruir un objeto hará que las referencias al objeto dejen de ser válidas y, en la gestión manual de la memoria, las referencias existentes se convertirán en referencias colgantes. En la recolección de basura (tanto la recolección de basura de seguimiento como el recuento de referencias), los objetos solo se destruyen cuando no hay referencias a ellos, pero la finalización puede crear nuevas referencias al objeto y, para evitar referencias colgantes, se produce la resurrección del objeto para que las referencias sigan siendo válidas.

Ejemplos

C++

clase Foo {} público: // Estas son las declaraciones prototipo de los constructores. Foo()int x); Foo()int x, int Sí.); // Constructor sobrecargado. Foo()const Foo "viejo); // Copia de Constructor. ~Foo(); // Destructor.};Foo::Foo()int x) {} // This is the implementation of // el constructor de un grupo.}Foo::Foo()int x, int Sí.) {} // This is the implementation of // el constructor de dos brazos.}Foo::Foo()const Foo "viejo) {} // This is the implementation of // el constructor de copia.}Foo::~Foo() {} // Esta es la implementación del destructor.}int principal() {} Foo Foo()14); // Llama al primer constructor. Foo foo2()12, 16); // Llama al constructor sobrecargado. Foo Foo3()Foo); // Llama al constructor de copias. // Destructores llamados en orden atrasado - Aquí, automáticamente.}

Java

clase Foo{} público Foo()int x) {} // This is the implementation of // el constructor de un solo brazo } público Foo()int x, int Sí.) {} // This is the implementation of // el constructor de dos brazos } público Foo()Foo viejo) {} // This is the implementation of // el constructor de copia } público estática vacío principal()String[] args) {} Foo Foo = nuevo Foo()14); // llamada primer constructor Foo foo2 = nuevo Foo()12, 16); // llamada constructor sobrecargado Foo Foo3 = nuevo Foo()Foo); // llamar al constructor de copia // colección de basura ocurre bajo las cubiertas, y los objetos son destruidos }}

C#

namespace ObjectLifeTime;clase Foo{} público Foo() {} // This is the implementation of // constructor predeterminado. } público Foo()int x) {} // This is the implementation of // el constructor de un grupo. } ~Foo() {} // This is the implementation of // el destructor. } público Foo()int x, int Sí.) {} // This is the implementation of // el constructor de dos brazos. }  público Foo()Foo viejo) {} // This is the implementation of // el constructor de copia. }  público estática vacío Main()cuerda[] args) {} Var defaultfoo = nuevo Foo(); // Constructor predeterminado de llamadas Var Foo = nuevo Foo()14); // Call first constructor Var foo2 = nuevo Foo()12, 16); // Call overloaded constructor Var Foo3 = nuevo Foo()Foo); // Llama al constructor de copia }}

Objetivo-C

#importación #objc/Object.h]@interface Punto: Objeto{} doble x; doble Sí.;}//Estos son los métodos de clase; hemos declarado dos constructores+ ()Punto *) nuevo ConX: ()doble) Y: ()doble);+ ()Punto *) nuevo ConR: ()doble) andTheta: ()doble);/ Métodos de posición- ()Punto *) setFirstCoord: ()doble);- ()Punto *) setSecondCoord: ()doble);/* Puesto que Point es una subclase del objeto genérico  * clase, ya ganamos asignación genérica e inicialización * métodos, +alloc y -init. Para nuestros constructores específicos * podemos hacer esto de estos métodos que tenemos * heredado. */@end @implementation Punto- ()Punto *) setFirstCoord: ()doble) new_val{} x = new_val;}- ()Punto *) setSecondCoord: ()doble) new_val{} Sí. = new_val;}+ ()Punto *) nuevo ConX: ()doble) x_val Y: ()doble) Y_val{} // Método de clase concisamente escrito para asignar y  //performe inicialización específica. retorno [[[[[]]Punto alloc] setFirstCoord:x_val] setSecondCoord:Y_val]; }+ ()Punto *) nuevo ConR: ()doble) r_val yTheta: ()doble) theta_val{} //En lugar de realizar lo mismo que lo anterior, podemos subyugarnos //utilizar el mismo resultado del método anterior retorno [Punto nuevo ConX:r_val Y:theta_val];}@endintprincipal()vacío){} //Construye dos puntos, p y q. Punto *p = [Punto nuevo ConX:4.0 Y:5.0]; Punto *q = [Punto nuevo ConR:1.0 andTheta:2.28]; //... texto del programa...  //Estamos acabados con p, digamos, así que, libre. //Si p asigna más memoria para sí mismo, puede ser necesario //override El método libre de objetos para repetir //libre memoria de p's. Pero este no es el caso, así que podemos [p gratis]; //...más texto... [q gratis]; retorno 0;}

Objeto Pascual

Idiomas relacionados: "Delphi", "Free Pascal", "Mac Pascal".

programa Ejemplo;Tipo DimensionEnum = () deUnassigned, de2D, de3D, de4D ); PointClass = clase privado Dimensión: DimensionEnum; público X: Integer; Y: Integer; Z: Integer; T: Integer; público (* prototipo de constructores *) constructor Crear(); constructor Crear()AX, AY: Integer); constructor Crear()AX, AY, AZ: Integer); constructor Crear()AX, AY, AZ, ATime: Integer); constructor CreateCopy()APoint: PointClass); (* prototipo de destructores *) destructor Destrucción; final;constructor PointClass.Crear();comenzar // implementación de un constructor genérico y no argumental Yo.Dimensión := deUnassigned;final;constructor PointClass.Crear()AX, AY: Integer);comenzar // implementación de un, 2 constructor de argumentos Yo.X := AX; Y := AY; Yo.Dimensión := de2D;final;constructor PointClass.Crear()AX, AY, AZ: Integer);comenzar // implementación de un, 3 constructor de argumentos Yo.X := AX; Y := AY; Yo.X := AZ; Yo.Dimensión := de3D;final;constructor PointClass.Crear()AX, AY, AZ, ATime: Integer);comenzar // implementación de un, 4 constructor de argumentos Yo.X := AX; Y := AY; Yo.X := AZ; T := ATime; Yo.Dimensión := de4D;final;constructor PointClass.CreateCopy()APoint: PointClass);comenzar // implementación de un constructor "copia" APoint.X := AX; APoint.Y := AY; APoint.X := AZ; APoint.T := ATime; Yo.Dimensión := de4D;final;destructor PointClass.PointClass.Destrucción;comenzar // implementación de un destructor genérico y no argumental Yo.Dimensión := deUnAssigned;final;Var (* variable para la asignación estática *) S: PointClass; (* variable para la asignación dinámica *) D: ^PointClass;comenzar (* del programa *) (* línea de vida del objeto con asignación estática *) S.Crear()5, 7); (* hacer algo con "S" *) S.Destrucción;  (* línea de vida del objeto con asignación dinámica *) D = nuevo PointClass, Crear()5, 7); (* hacer algo con "D" *) Desechos D, Destrucción;final. (* del programa *)

Pitón

clase Socket: def __init_()auto, remoto_host: str) - Ninguno: # Connect to remote host def Enviar()auto): # Enviar datos def recreo()auto): # Recibir datos def cerca()auto): # Cierra el socket def __del_()auto): # __del_ función mágica llamada cuando la referencia del objeto es igual a cero auto.cerca()def f(): socket = Socket()"example.com") socket.Enviar()"prueba") retorno socket.recreo()

Socket se cerrará en la siguiente ronda de recolección de basura después de "f" La función se ejecuta y regresa, ya que se han perdido todas las referencias a ella.

Contenido relacionado

DNS (desambiguación)

DNS es el Sistema de Nombres de Dominio, un sistema de red utilizado para traducir nombres a direcciones...

KL0

Kernel Language 0 es un lenguaje de programación de lógica secuencial basado en Prolog, utilizado en el proyecto informático ICOT de quinta...

Computación parasitaria

La computación parasitaria es una técnica de programación en la que un programa en interacciones normales autorizadas con otro programa logra que el otro...
Más resultados...
Tamaño del texto:
undoredo
format_boldformat_italicformat_underlinedstrikethrough_ssuperscriptsubscriptlink
save