2012-10-12 241 views
9

Aquí tengo una situación complicada, supongo. Necesito poder liberar un objeto que es un campo de un registro. Normalmente escribiría el código de limpieza en el destructor, si fuera una clase. Pero como los tipos de registro no pueden introducir un "destructor", ¿cómo sería posible llamar al TObject (campo) .Free;?¿Cómo liberar un objeto que está en un registro?

Habrá dos tipos de uso: Predigo

  1. Sustitución del registro con una nueva.

    Creo que este uso sería fácil de implementar. Dado que los registros son tipos de valores y, por lo tanto, se copian en la asignación, puedo sobrecargar el operador de asignación y liberar los objetos que pertenecen al registro anterior.

    (Editar: sobrecarga asignación no fue capaz Esa es una información nueva para mi ...)

  2. Al salir del ámbito donde variable de registro definido.

    Puedo pensar en un método privado que libera los objetos y este método podría ser invocado en la excitación de alcance manualmente. PERO, aquí está la misma pregunta: ¿Cómo hacer que sea más sin precedentes? Este comportamiento tipo de se siente como una clase ...

Este es un ejemplo (y obviamente no el uso previsto):

TProperties = record 
    ... some other spesific typed fields: Integers, pointers etc.. 
    FBaseData: Pointer; 

    FAdditionalData: TList<Pointer>; 
    //FAdditionalData: array of Pointer; this was the first intended definition 
end; 

Supongamos,

FAdditionalData:=TList<Pointer>.Crete; 

llama en el constructor de registros o manualmente en el alcance de la variable de registro accediendo públicamente al campo como

procedure TFormX.ButtonXClick(Sender: TObject); 
var 
    rec: TProperties; 
begin 
    //rec:=TProperties.Create(with some parameters); 

    rec.FAdditionalData:=TList<Pointer>.Create; 

    //do some work with rec 
end; 

Después de salir del ámbito ButtonClick la rec ya no existe más que un TList aún conserva su existencia lo que provoca pérdidas de memoria a ...

+1

La asignación de registros no se puede sobrecargar. – kludg

+0

No estaba al tanto de eso (nunca antes lo había necesitado), pero lo he aprendido ahora :) Sí, no pudo ser sobrecargado ... –

Respuesta

10

Si todo lo que tienes en el registro es una referencia de objeto, entonces no puedes hacer que el compilador te ayude. Usted está a cargo exclusivo de la duración de ese objeto. No puede sobrecargar al operador de asignación y no recibe ninguna notificación de finalización del alcance.

Lo que puedes hacer es agregar una interfaz de guardia que administrará la vida útil del objeto.

TMyRecord = record 
    obj: TMyObject; 
    guard: IInterface; 
end; 

Usted necesita asegurarse de que TMyObject gestiona su vida útil mediante recuento de referencia. Por ejemplo derivando de TInterfacedObject.

Al inicializar el registro que hace esto:

rec.obj := TMyObject.Create; 
rec.guard := rec.obj; 

En este punto, el campo del registro guard ahora gestionará la vida de su objeto.

De hecho, si desea ampliar esta idea, puede crear una clase dedicada para proteger la vida útil de los objetos. Eso ya no te obliga a implementar IInterface en tu clase. Hay muchos ejemplos en la web que ilustran la técnica. Por ejemplo, ofrezco el artículo de Jarrod Hollingworth titulado Smart Pointers, y el de Barry Kelly titulado Reference-counted pointers, revisited. Hay muchos más por ahí. Es un viejo truco!

Sin embargo, tenga en cuenta que lo que tiene aquí es un extraño híbrido de tipo de valor y tipo de referencia. A primera vista, los registros son tipos de valores. Sin embargo, este actúa como un tipo de referencia. Si tiene otros campos en el registro que son tipos de valor, eso sería aún más confuso. Tendrá que ser muy consciente de este problema cuando trabaje con dicho registro.

En vista de ello, sin saber más sobre su diseño, me inclino a aconsejarle que no ponga referencias a objetos en los registros. Se ajustan mejor dentro de los tipos de referencia, es decir, clases.

+7

Sugiero liderar primero con el último párrafo. Poner clases en los registros es una ruta rápida al código de errores si uno no es muy, muy cuidadoso con la administración de por vida. – afrazier

+0

Respuesta muy informativa, gracias Sr. Heffernan. Voy a experimentar esas formas. Necesito actualizar la pregunta para ser más claro con el diseño. Los campos del objeto son ** TList ** s, pero parece que volvería a ser lo que era en primer lugar: ** matriz de puntero ** –

+1

El uso de una matriz dinámica resuelve la gestión del tiempo de vida. Podría considerar utilizar el excelente 'TDynArray' de Synopse para facilitar el uso de matrices dinámicas. –

3

Recuerdo que alguien creó una clase llamada TLifetimeWatcher. Básicamente, parece que:

TLifetimeWatcher = class(TInterfacedObject) 
private 
    fInstance: TObject; 
    fProc: TProc; 
public 
    constructor Create(instance: TObject); overload; 
    constructor Create(instance: TObject; proc: TProc); overload; 
    destructor Destroy; override; 
end; 

// El (limpieza) Proc se ejecutará en el destructor si se asigna, de lo contrario la instancia serán liberados mediante la invocación del método gratuito.

+0

Este es el enfoque básico que se describe en mi respuesta. Sin la implementación, no es de mucha utilidad. –

+2

'Alguien' es Barry Kelly - http://blog.barrkel.com/2008/09/smart-pointers-in-delphi.html – kludg

Cuestiones relacionadas