Continuamos con la introducción al lenguaje Pascal Moderno.

Esta entrada es una traducción al español, del texto original escrito por Michalis Kamburelis.

Liberando las clases

Recuerde liberar todas las instancias de las clases

Las instancias de clase deben liberarse manualmente, de lo contrario, se producen pérdidas de memoria. Recomiendo usar las opciones FPC -gl -gh para detectar fugas de memoria (ver https://castle-engine.io/manual_optimization.php#section_memory).

Tenga en cuenta que esto no se refiere a las excepciones planteadas. Aunque creas una clase cuando generas una excepción (y es una clase perfectamente normal, y también puedes crear tus propias clases para este propósito). Pero esta instancia de clase se libera automáticamente.

¿Cómo liberar?

Para liberar la instancia de clase, es mejor llamar a FreeAndNil(A) desde la unidad SysUtils en su instancia de clase. Comprueba si A es nil, si no, llama a su destructor y establece A en nil. Entonces llamarlo muchas veces seguidas no es un error.

Es más o menos un atajo para

En realidad, eso es una simplificación excesiva, ya que FreeAndNil hace un truco útil y establece la variable A en nil antes de llamar al destructor en una referencia adecuada. Esto ayuda a prevenir cierta clase de errores — la idea es que el código «externo» nunca debería acceder a una instancia de la clase medio destruida.

A menudo, también verá personas que usan el método A.Free, que es como hacer

Esto libera la A, a menos que sea nula. Tenga en cuenta que, en circunstancias normales, nunca debe llamar a un método en una instancia que puede ser nula. Entonces, la llamada A.Free puede parecer sospechosa a primera vista, si A puede ser nula. Sin embargo, el método gratuito es una excepción a esta regla. Hace algo sucio en la implementación, es decir, comprueba si Self <> nil.

Nota: Este truco sucio (permitir oficialmente que el método se use con Self equal nil) solo es posible para métodos no virtuales. Y siempre que Self = nil sea posible, el método no puede llamar a ningún método virtual ni acceder a ningún campo, ya que estos se bloquearían con una violación de acceso cuando se llamaran en un puntero nulo. Consulte el código de ejemplo method_with_self_nil.lpr. No recomendamos usar este truco en su propio código (para métodos virtuales o no virtuales), ya que es contrario a la intuición del uso normal; en general, todos los métodos de instancia deberían poder asumir que funcionan en una instancia válida (no nula). y puede acceder a los campos y llamar a cualquier otro método (virtual o no).

Aconsejo usar FreeAndNil(A) siempre, sin excepciones, y nunca llamar directamente al método Free o Destroy destructor. Castle Game Engine lo hace así. Ayuda mantener una buena afirmación de que todas las referencias son nulas o apuntan a instancias válidas.

Liberación manual y automática

En muchas situaciones, la necesidad de liberar la instancia no es un gran problema. Simplemente escribe un destructor, que coincide con un constructor, y desasigna todo lo que se asignó en el constructor (o, más completamente, en toda la vida útil de la clase). Tenga cuidado de liberar cada cosa solo una vez. Por lo general, es una buena idea establecer la referencia liberada en cero, por lo general, es más cómodo hacerlo llamando a FreeAndNil (A).

Entonces, así:

Para evitar la necesidad de liberar explícitamente la instancia, también se puede usar la función TComponent de «propiedad». Un objeto que es propiedad será liberado automáticamente por el propietario. El mecanismo es inteligente y nunca liberará una instancia ya liberada (por lo que las cosas también funcionarán correctamente si libera manualmente el objeto que posee antes). Podemos cambiar el ejemplo anterior por este:

Tenga en cuenta que necesitamos anular un constructor virtual de TComponent aquí. Entonces no podemos cambiar los parámetros del constructor. (En realidad, puede — declarar un nuevo constructor con reintroduce. Pero tenga cuidado, ya que algunas funcionalidades, por ejemplo, la transmisión, aún usarán el constructor virtual, así que asegúrese de que funcione correctamente en cualquier caso).

Tenga en cuenta que siempre puede usar un valor nulo para el propietario. De esta forma, el mecanismo de «propiedad» no se utilizará para este componente. Tiene sentido si necesita usar el descendiente de TComponent, pero siempre desea liberarlo manualmente. Para ello, crearía un componente descendiente como este: ManualGun := TGun.Create(nil);.

Otro mecanismo para la liberación automática es la funcionalidad OwnsObjects (¡por defecto ya es cierto!) de clases de lista como TFPGObjectList o TObjectList. Entonces también podemos escribir:

Tenga en cuenta que el mecanismo de «propiedad» de las clases de lista es simple y obtendrá un error si libera la instancia utilizando otros medios, mientras que también está contenida en una lista. Utilice el método Extraer para eliminar algo de una lista sin liberarlo, asumiendo así la responsabilidad de liberarlo usted mismo.

En Castle Game Engine: Los descendientes de TX3DNode tienen administración de memoria automática cuando se insertan como hijos de otro TX3DNode. El nodo raíz X3D, TX3DRootNode, a su vez, suele ser propiedad de TCastleSceneCore. Algunas otras cosas también tienen un mecanismo de propiedad simple: busque parámetros y propiedades llamados OwnsXxx.

El destructor virtual llamado Destroy

Como viste en los ejemplos anteriores, cuando se destruye la clase, se llama a su destructor llamado Destroy.

En teoría, podría tener múltiples destructores, pero en la práctica casi nunca es una buena idea. Es mucho más fácil tener un solo destructor llamado Destroy, que a su vez es llamado por el método Free, que a su vez es llamado por el procedimiento FreeAndNil.

El destructor Destroy en TObject se define como un método virtual, por lo que siempre debe marcarlo con la palabra clave override en todas sus clases (ya que todas las clases descienden de TObject). Esto hace que el método Free funcione correctamente. Recuerde cómo funcionan los métodos virtuales a partir de Métodos virtuales, anule y vuelva a introducir.

Consejo: Esta información sobre los destructores es, de hecho, inconsistente con los constructores.

Es normal que una clase tenga múltiples constructores. Por lo general, todos se llaman Create y solo tienen diferentes parámetros, pero también está bien inventar otros nombres para los constructores.

Además, el constructor Create en TObject no es virtual, por lo que no lo marca con anulación en los descendientes.

Todo esto le brinda un poco de flexibilidad adicional al definir constructores. A menudo no es necesario hacerlos virtuales, por lo que, de forma predeterminada, no está obligado a hacerlo.

Tenga en cuenta, sin embargo, que esto cambia para los descendientes de TComponent. El TComponent define un constructor virtual Create(AOwner: TComponent). Necesita un constructor virtual para que funcione el sistema de transmisión. Al definir los descendientes del TComponent, debe anular este constructor (y marcarlo con la palabra clave anular) y realizar toda su inicialización dentro de él. Todavía está bien definir constructores adicionales, pero solo deben actuar como «ayudantes». La instancia siempre debería funcionar cuando se crea con el constructor Create(AOwner: TComponent); de lo contrario, no se construirá correctamente durante la transmisión. La transmisión se utiliza, p. al guardar y cargar este componente en un formulario de Lazarus.

Notificación de liberación

Si copia una referencia a la instancia, de modo que tiene dos referencias a la misma memoria, y luego una de ellas se libera, la otra se convierte en un «puntero colgante». No se debe acceder, ya que apunta a una memoria que ya no está asignada. Acceder a él puede resultar en un error de tiempo de ejecución, o que se devuelva basura (ya que la memoria puede reutilizarse para otras cosas en su programa).

Usar FreeAndNil para liberar la instancia no ayuda aquí. FreeAndNil establece en cero solo la referencia que obtuvo, no hay forma de que establezca todas las demás referencias automáticamente. Considere este código:

Al final de este bloque, el Obj1 es nulo. Si algún código tiene que acceder a él, puede usar de manera confiable si Obj1 <> nil entonces… para evitar llamar a métodos en una instancia liberada, como

Intentar acceder a un campo de una instancia nula da como resultado una excepción predecible en tiempo de ejecución.

Entonces, incluso si algún código no verificará Obj1 <> nil, y accederá ciegamente al campo Obj1, obtendrá una clara excepción en tiempo de ejecución. Lo mismo ocurre con llamar a un método virtual o llamar a un método no virtual que accedió a un campo de una instancia nula.

Con Obj2, las cosas son menos predecibles. No es nulo, pero no es válido. Intentar acceder a un campo de una instancia no válida no nula da como resultado un comportamiento impredecible — tal vez una excepción de violación de acceso, tal vez una devolución de datos basura.

Hay varias soluciones para ello:

  • Una solución es, bueno, tener cuidado y leer la documentación. No suponga nada sobre la vida útil de la referencia, si se crea mediante otro código. Si una clase TCar tiene un campo que apunta a alguna instancia de TWheel, es una convención que la referencia a la rueda sea válida mientras exista la referencia al automóvil, y el automóvil liberará sus ruedas dentro de su destructor. Pero eso es solo una convención, la documentación debe mencionar si está sucediendo algo más complicado.
  • En el ejemplo anterior, justo después de liberar la instancia de Obj1, simplemente puede configurar la variable Obj2 explícitamente en nil. Eso es trivial en este caso simple.
  • La solución más preparada para el futuro es utilizar el mecanismo de «notificación gratuita» de la clase TComponent. Se puede notificar a un componente cuando se libera otro componente y, por lo tanto, establecer su referencia en nil.
  • Por lo tanto, obtienes algo así como una referencia débil. Puede hacer frente a varios escenarios de uso, por ejemplo, puede dejar que el código externo a la clase establezca su referencia, y el código externo también puede liberar la instancia en cualquier momento.
  • Esto requiere que ambas clases desciendan de TComponent. Usarlo en general se reduce a llamar a FreeNotification , RemoveFreeNotification y anular Notificación.
  • Esto requiere que ambas clases desciendan de TComponent. Usarlo en general se reduce a llamar a FreeNotification , RemoveFreeNotification y anular Notificación.

Aquí hay un ejemplo completo que muestra cómo usar este mecanismo, junto con el constructor/destructor y una propiedad setter. A veces se puede hacer más simple, pero esta es la versión completa que siempre es correcta 🙂

Observador de notificaciones gratuito (Castle Game Engine)

En Castle Game Engine recomendamos usar TFreeNotificationObserver de la unidad CastleClassUtils en lugar de llamar directamente a FreeNotification, RemoveFreeNotification y anular Notificación.

En general, usar TFreeNotificationObserver parece un poco más simple que usar el mecanismo FreeNotification directamente (aunque admito que es cuestión de gustos). Pero, en particular, cuando se debe observar la misma instancia de clase por múltiples razones, TFreeNotificationObserver es mucho más fácil de usar (el uso directo de FreeNotification en este caso puede complicarse, ya que debe estar atento para no cancelar el registro de la notificación demasiado pronto).

Este es el código de ejemplo que usa TFreeNotificationObserver, para lograr el mismo efecto que el ejemplo en la sección anterior:

Ver https://castle-engine.io/custom_components

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio esta protegido por reCAPTCHA y laPolítica de privacidady losTérminos del servicio de Googlese aplican.

El periodo de verificación de reCAPTCHA ha caducado. Por favor, recarga la página.