delphi memory-management object-lifetime try-finally

delphi - La mejor práctica para hacer una declaración TRY/FINALY anidada



memory-management object-lifetime (6)

Hola, ¿cuál es la mejor manera de hacer declaraciones try y finally anidadas en delphi?

var cds1 : TClientDataSet; cds2 : TClientDataSet; cds3 : TClientDataSet; cds4 : TClientDataSet; begin cds1 := TClientDataSet.Create(application ); try cds2 := TClientDataSet.Create(application ); try cds3 := TClientDataSet.Create(application ); try cds4 := TClientDataSet.Create(application ); try /////////////////////////////////////////////////////////////////////// /// DO WHAT NEEDS TO BE DONE /////////////////////////////////////////////////////////////////////// finally cds4.free; end; finally cds3.free; end; finally cds2.free; end; finally cds1.free; end; end;

¿Puedes sugerir una mejor manera de hacer esto?


¿qué tal lo siguiente?

var cds1 : TClientDataSet; cds2 : TClientDataSet; cds3 : TClientDataSet; cds4 : TClientDataSet; begin cds1 := Nil; cds2 := Nil; cds3 := Nil; cds4 := Nil; try cds1 := TClientDataSet.Create(nil); cds2 := TClientDataSet.Create(nil); cds3 := TClientDataSet.Create(nil); cds4 := TClientDataSet.Create(nil); /////////////////////////////////////////////////////////////////////// /// DO WHAT NEEDS TO BE DONE /////////////////////////////////////////////////////////////////////// finally freeandnil(cds4); freeandnil(cds3); freeandnil(cds2); freeandnil(Cds1); end; end;

Esto lo mantiene compacto, y solo intenta liberar las instancias que se crearon. Realmente no hay necesidad de realizar el anidamiento, ya que CUALQUIER falla dará como resultado finalmente la finalización y la realización de toda la limpieza en el ejemplo que proporcionó.

Personalmente trato de no anidar dentro del mismo método ... con la excepción de un escenario try / try / except / finally. Si encuentro que necesito anidar, entonces para mí ese es un buen momento para pensar refactorizar en otra llamada a método.

EDIT Se mghie un poco gracias a los comentarios de mghie y utku .

EDIT cambió la creación del objeto para que no sea una aplicación de referencia, ya que no es necesario en este ejemplo.


@mghie: Delphi tiene objetos asignados de pila:

type TMyObject = object private FSomeField: PInteger; public constructor Init; destructor Done; override; end; constructor TMyObject.Init; begin inherited Init; New(FSomeField); end; destructor TMyObject.Done; begin Dispose(FSomeField); inherited Done; end; var MyObject: TMyObject; begin MyObject.Init; /// ... end;

Lamentablemente, como se muestra en el ejemplo anterior, los objetos asignados a la pila no evitan fugas de memoria.

Así que esto aún requeriría una llamada al destructor de esta manera:

var MyObject: TMyObject; begin MyObject.Init; try /// ... finally MyObject.Done; end; end;

De acuerdo, lo admito, esto está muy fuera del tema, pero pensé que podría ser interesante en este contexto ya que los objetos asignados a la pila se mencionaron como una solución (que no lo son si no hay una llamada automática al destructor).


Hay otra variación del código sin intento anidado ... finalmente eso solo se me ocurrió. Si no crea los componentes con el parámetro AOwner del constructor establecido en cero, entonces simplemente puede hacer uso de la administración de por vida que la VCL le da de forma gratuita:

var cds1: TClientDataSet; cds2: TClientDataSet; cds3: TClientDataSet; cds4: TClientDataSet; begin cds1 := TClientDataSet.Create(nil); try // let cds1 own the other components so they need not be freed manually cds2 := TClientDataSet.Create(cds1); cds3 := TClientDataSet.Create(cds1); cds4 := TClientDataSet.Create(cds1); /////////////////////////////////////////////////////////////////////// /// DO WHAT NEEDS TO BE DONE /////////////////////////////////////////////////////////////////////// finally cds1.Free; end; end;

Soy un gran creyente en el código pequeño (si no está demasiado ofuscado).


Hay un buen video sobre excepciones en constructores y destructores

Muestra algunos buenos ejemplos tales como:

var cds1 : TClientDataSet; cds2 : TClientDataSet; begin cds1 := Nil; cds2 := Nil; try cds1 := TClientDataSet.Create(nil); cds2 := TClientDataSet.Create(nil); /////////////////////////////////////////////////////////////////////// /// DO WHAT NEEDS TO BE DONE /////////////////////////////////////////////////////////////////////// finally freeandnil(cds2); //// what has if there in an error in the destructor of cds2 freeandnil(Cds1); end; end;

¿Qué pasa si hay un error en el destructor de cds2?

Cds1 no será destruido

EDITAR

Otro buen recurso es:

Jim McKeeth excelente video sobre el manejo de excepciones retrasadas en el rango de código III cuando habla de problemas en el manejo de excepciones en el bloque final.


Si quieres ir por esta (IMO) ruta desagradable (gestión de grupos con inicialización a cero para saber si es necesario liberar), al menos DEBES garantizar que no permitas que una excepción en uno de los destructor impida liberar el resto de tus objetos
Algo como:

function SafeFreeAndNil(AnObject: TObject): Boolean; begin try FreeAndNil(AnObject); Result := True; except Result := False; end; end; var cds1 : TClientDataSet; cds2 : TClientDataSet; IsOK1 : Boolean; IsOK2 : Boolean; begin cds1 := Nil; cds2 := Nil; try cds1 := TClientDataSet.Create(nil); cds2 := TClientDataSet.Create(nil); /////////////////////////////////////////////////////////////////////// /// DO WHAT NEEDS TO BE DONE /////////////////////////////////////////////////////////////////////// finally IsOk2 := SafeFreeAndNil(cds2); // an error in freeing cds2 won''t stop execution IsOK1 := SafeFreeAndNil(Cds1); if not(IsOk1 and IsOk2) then raise EWhatever.... end; end;


Yo usaría algo como esto:

var Safe: IObjectSafe; cds1 : TClientDataSet; cds2 : TClientDataSet; cds3 : TClientDataSet; cds4 : TClientDataSet; begin Safe := ObjectSafe; cds1 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet; cds2 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet; cds3 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet; cds4 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet; /////////////////////////////////////////////////////////////////////// /// DO WHAT NEEDS TO BE DONE /////////////////////////////////////////////////////////////////////// // if Safe goes out of scope it will be freed and in turn free all guarded objects end;

Para la implementación de la interfaz, consulte this artículo, pero puede crear fácilmente algo similar usted mismo.

EDITAR:

Acabo de notar que en el artículo vinculado Guard () es un procedimiento. En mi propio código he sobrecargado las funciones de Guard () que devuelven TObject, el código de ejemplo anterior asume algo similar. Por supuesto, con los genéricos, ahora es posible un código mucho mejor ...

EDICION 2:

Si se pregunta por qué lo intento ... finalmente se elimina por completo en mi código: es imposible eliminar los bloques anidados sin introducir la posibilidad de pérdidas de memoria (cuando los destructores generan excepciones) o violaciones de acceso. Por lo tanto, es mejor utilizar una clase de ayuda y dejar que el recuento de referencias de las interfaces se haga cargo por completo. La clase auxiliar puede liberar todos los objetos que protege, incluso si algunos de los destructores generan excepciones.