¿Por qué no debería usar “si está asignado ()” antes de acceder a los objetos?

Esta pregunta es una continuación de un comentario particular de personas en stackoverflow que he visto algunas veces diferentes ahora. Yo, junto con el desarrollador que me enseñó Delphi, para mantener las cosas seguras, siempre he puesto una marca if assigned() antes de liberar objetos, y antes de hacer otras cosas. Sin embargo, ahora me dicen que no debería agregar este cheque. Me gustaría saber si existe alguna diferencia en cómo la aplicación se comstack / ejecuta si hago esto, o si no afectará el resultado en absoluto …

 if assigned(SomeObject) then SomeObject.Free; 

Digamos que tengo un formulario, y estoy creando un objeto de bitmap en el fondo sobre la creación del formulario, y liberándolo cuando termine con él. Ahora supongo que mi problema es que me acostumbré demasiado a poner esta marca en gran parte de mi código cuando bash acceder a objetos que podrían haber sido liberados en algún momento. Lo he estado usando incluso cuando no es necesario. Me gusta ser completo …

 unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private FBitmap: TBitmap; public function LoadBitmap(const Filename: String): Bool; property Bitmap: TBitmap read FBitmap; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); begin FBitmap:= TBitmap.Create; LoadBitmap('C:\Some Sample Bitmap.bmp'); end; procedure TForm1.FormDestroy(Sender: TObject); begin if assigned(FBitmap) then begin //<----- //Do some routine to close file FBitmap.Free; end; end; function TForm1.LoadBitmap(const Filename: String): Bool; var EM: String; function CheckFile: Bool; begin Result:= False; //Check validity of file, return True if valid bitmap, etc. end; begin Result:= False; EM:= ''; if assigned(FBitmap) then begin //<----- if FileExists(Filename) then begin if CheckFile then begin try FBitmap.LoadFromFile(Filename); except on e: exception do begin EM:= EM + 'Failure loading bitmap: ' + e.Message + #10; end; end; end else begin EM:= EM + 'Specified file is not a valid bitmap.' + #10; end; end else begin EM:= EM + 'Specified filename does not exist.' + #10; end; end else begin EM:= EM + 'Bitmap object is not assigned.' + #10; end; if EM  '' then begin raise Exception.Create('Failed to load bitmap: ' + #10 + EM); end; end; end. 

Ahora digamos que estoy presentando un nuevo objeto de lista personalizado llamado TMyList de TMyListItem . Para cada elemento en esta lista, por supuesto tengo que crear / liberar cada objeto del artículo. Hay algunas formas diferentes de crear un elemento, así como algunas formas diferentes de destruir un elemento (Agregar / Eliminar es el más común). Estoy seguro de que es una muy buena práctica poner aquí esta protección …

 procedure TMyList.Delete(const Index: Integer); var I: TMyListItem; begin if (Index >= 0) and (Index < FItems.Count) then begin I:= TMyListItem(FItems.Objects[Index]); if assigned(I) then begin //<----- if I  nil then begin I.DoSomethingBeforeFreeing('Some Param'); I.Free; end; end; FItems.Delete(Index); end else begin raise Exception.Create('My object index out of bounds ('+IntToStr(Index)+')'); end; end; 

En muchos escenarios, al menos espero que el objeto aún se cree antes de intentar liberarlo. Pero nunca se sabe qué deslizamientos pueden ocurrir en el futuro cuando un objeto se libera antes de lo previsto. Siempre he usado este cheque, pero ahora me dicen que no debería, y todavía no entiendo por qué.


EDITAR

Aquí hay un ejemplo para tratar de explicarte por qué tengo el hábito de hacer esto:

 procedure TForm1.FormDestroy(Sender: TObject); begin SomeCreatedObject.Free; if SomeCreatedObject = nil then ShowMessage('Object is nil') else ShowMessage('Object is not nil'); end; 

Mi punto es que if SomeCreatedObject nil no es lo mismo que if Assigned(SomeCreatedObject) porque después de liberar SomeCreatedObject , no se evalúa como nil . Por lo tanto, ambos controles deberían ser necesarios.

Esta es una pregunta muy amplia con muchos angularjs diferentes.

El significado de la función Assigned

Gran parte del código en su pregunta revela una comprensión incorrecta de la función Assigned . La documentación dice esto:

Prueba un puntero nulo (no asignado) o una variable de procedimiento.

Use Asignado para determinar si el puntero o procedimiento al que hace referencia P es nulo. P debe ser una referencia variable de un puntero o tipo de procedimiento. Asignado (P) corresponde a la prueba P <> nil para una variable de puntero, y @P <> nil para una variable de procedimiento.

Retornos asignados Falso si P es nulo, Verdadero en caso contrario.

Nota : Assigned no puede detectar un puntero colgante, es decir, uno que no es nulo pero que ya no apunta a datos válidos. Por ejemplo, en el ejemplo de código para Asignado, Asignado no detecta que P no es válido.

Los puntos clave a tomar de esto son:

  1. Assigned es equivalente a probar <> nil .
  2. Assigned no puede detectar si el puntero o la referencia del objeto es válido o no.

Lo que esto significa en el contexto de esta pregunta es que

 if obj<>nil 

y

 if Assigned(obj) 

son completamente intercambiables.

Prueba Assigned antes de llamar Free

La implementación de TObject.Free es muy especial.

 procedure TObject.Free; begin if Self <> nil then Destroy; end; 

Esto le permite llamar Free a una referencia de objeto que es nil y hacerlo no tiene ningún efecto. Por lo que vale, no tengo conocimiento de ningún otro lugar en el RTL / VCL donde se usa ese truco.

La razón por la que desea permitir que se llame a Free en una referencia de objeto nil proviene de la forma en que los constructores y los destructores operan en Delphi.

Cuando se genera una excepción en un constructor, se llama al destructor. Esto se hace con el fin de desasignar los recursos asignados en la parte del constructor que tuvo éxito. Si Free no se implementó como está, los destructores tendrían que verse así:

 if obj1 <> nil then obj1.Free; if obj2 <> nil then obj2.Free; if obj3 <> nil then obj3.Free; .... 

La siguiente pieza del rompecabezas es que los constructores de Delphi inicializan la memoria de la instancia a cero . Esto significa que los campos de referencia de objetos no asignados son nil .

Pon todo esto junto y el código destructor ahora se convierte

 obj1.Free; obj2.Free; obj3.Free; .... 

Debe elegir la última opción porque es mucho más legible.

Hay un escenario donde necesita probar si la referencia está asignada en un destructor. Si necesita llamar a cualquier método en el objeto antes de destruirlo, entonces debe evitar la posibilidad de que sea nil . Entonces este código correría el riesgo de un AV si apareciera en un destructor:

 FSettings.Save; FSettings.Free; 

En cambio, escribes

 if Assigned(FSettings) then begin FSettings.Save; FSettings.Free; end; 

Prueba Assigned fuera de un destructor

También hablas de escribir código defensivo fuera de un destructor. Por ejemplo:

 constructor TMyObject.Create; begin inherited; FSettings := TSettings.Create; end; destructor TMyObject.Destroy; begin FSettings.Free; inherited; end; procedure TMyObject.Update; begin if Assigned(FSettings) then FSettings.Update; end; 

En esta situación, tampoco es necesario probar Assigned en TMyObject.Update . La razón es que simplemente no puede llamar a TMyObject.Update menos que el constructor de TMyObject . Y si el constructor de TMyObject tuvo éxito, entonces usted está seguro de que se le asignó FSettings . Así que de nuevo haces que tu código sea mucho menos legible y más difícil de mantener al realizar llamadas espurias a Assigned .

Hay un escenario en el que debe escribir if Assigned y es allí donde la existencia del objeto en cuestión es opcional. Por ejemplo

 constructor TMyObject.Create(UseLogging: Boolean); begin inherited Create; if UseLogging then FLogger := TLogger.Create; end; destructor TMyObject.Destroy; begin FLogger.Free; inherited; end; procedure TMyObject.FlushLog; begin if Assigned(FLogger) then FLogger.Flush; end; 

En este escenario, la clase admite dos modos de operación, con y sin registro. La decisión se toma en el momento de la construcción y cualquier método que se refiera al objeto de registro debe probar su existencia.

Esta forma no común de código hace que sea aún más importante que no utilice llamadas falsas a Assigned para objetos no opcionales. Cuando vea if Assigned(FLogger) en el código que debería ser una clara indicación de que la clase puede funcionar normalmente con FLogger no existe. Si rocías llamadas gratuitas a Assigned alrededor de tu código, entonces pierdes la capacidad de decir de un vistazo si un objeto siempre debería existir o no.

Free tiene alguna lógica especial: comprueba si Self es nil , y si es así, vuelve sin hacer nada, por lo que puedes llamar a X.Free forma X.Free incluso si X es nil . Esto es importante cuando escribes destructores: David tiene más detalles en su respuesta .

Puede ver el código fuente de forma Free para ver cómo funciona. No tengo a mano la fuente Delphi, pero es algo como esto:

 procedure TObject.Free; begin if Self <> nil then Destroy; end; 

O, si lo prefiere, podría considerarlo como el código equivalente usando Assigned :

 procedure TObject.Free; begin if Assigned(Self) then Destroy; end; 

Puede escribir sus propios métodos que comprueben if Self <> nil , siempre que sean métodos de instancia estáticos (es decir, no virtual o dynamic ) (gracias a David Heffernan para el enlace de documentación). Pero en la biblioteca Delphi, Free es el único método que conozco que utiliza este truco.

Por lo tanto, no necesita verificar si la variable está Assigned antes de llamar a Free ; ya lo hace por ti. En realidad, esa es la razón por la cual la recomendación es llamar a Free lugar de llamar directamente a Destroy : si llamaste a Destroy en una referencia nil , obtendrías una infracción de acceso.

Por qué no deberías llamar

 if Assigned(SomeObject) then SomeObject.Free; 

Simplemente porque ejecutarías algo como esto

 if Assigned(SomeObject) then if Assigned(SomeObject) then SomeObject.Destroy; 

Si llama solo SomeObject.Free; entonces es solo

  if Assigned(SomeObject) then SomeObject.Destroy; 

Para su actualización, si tiene miedo de la referencia de la instancia del objeto, use FreeAndNil. Destruirá y desreferenciará tu objeto

 FreeAndNil(SomeObject); 

Es similar a si llamas

 SomeObject.Free; SomeObject := nil; 

No estoy completamente seguro de eso, pero parece:

 if assigned(object.owner) then object.free 

funciona bien. En este ejemplo, sería

 if assigned(FBitmap.owner) then FBitmap.free