Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   OOP (https://www.clubdelphi.com/foros/forumdisplay.php?f=5)
-   -   Estudiando constructores y destructores (https://www.clubdelphi.com/foros/showthread.php?t=89595)

AgustinOrtu 20-12-2015 20:36:03

Estudiando constructores y destructores
 
Segun la documentacion, en el constructor de un objeto todas las referencias a otros objetos son asignadas automaticamente a NIL

Es decir, teniendo esta clase

Código Delphi [-]
  TTestClass = class
  public
    Object: TObject;
  end;

procedure Test;
var
  LTest: TTestClass;
begin
  LTest := TTestClass.Create;
  LTest.Object ---> NIL

  LTest.Object.Free --> Llamada segura, Free va a chequear que el objeto no es nil y no se ejecuta el destructor, no hay riesgo de Acess Violation
end;

Esto funciona de maravillas y no he tenido nunca problemas con eso (una vez que lo entendi claro)

Hasta que hoy, haciendo experimentos raros, se me ocurrio hacer esto:

Código Delphi [-]
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    FormFoo: TObject;
  end;

procedure TForm2.Button1Click(Sender: TObject);
var
  LocalFoo: TObject;
begin
  FormFoo.Free;

  LocalFoo.Free;
end;

Obviamente llamar a FomFoo.Free es seguro, de hecho el destructor no se ejecuta; las variables de instancia son seteadas a nil en los constructores de los objetos (que va, TForm es un objeto tambien, recuerda? :))

Pero el problema son las variables locales; es verdad, de hecho tiene logica, que si yo declaro una variable, y no la inicializo nunca, el valor no esta definido; es decir que una llamada a Free deberia arrojar una excepcion Acess Violation. Bueno, curiosa fue mi sorpresa al correr el codigo anterior: no se elevo ninguna excepcion.

Los invito a que ejecuten el mismo codigo :)

Para el que no se le ocurra, puede agregar la linea ShowMessage(LocalFoo.ClassName); antes del Free

escafandra 21-12-2015 00:28:54

Lo he ejecutado.
No se elevan excepciones pero se corrompe la app.
Si tratas de hacer esto:
Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
var
  LocalFoo: TObject;
  S: String;
begin
  FormFoo.Free;
  LocalFoo:= nil;
  S:= LocalFoo.ClassName;
  MessageBox(0,0,PCHAR(S),0);
  LocalFoo.Free;
end;

Aparece una excepcion en la línea
Código Delphi [-]
S:= LocalFoo.ClassName;

Si trato de hacer esto
Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
var
  LocalFoo: TObject;
begin
  FormFoo.Free;
  LocalFoo:= nil;
  ShowMessage(LocalFoo.ClassName);
  LocalFoo.Free;
end;
El antivirus se carga el ejecutable y no corre.


Saludos.

AgustinOrtu 21-12-2015 01:36:26

Curiosamente, usando el codigo que publicas me da error de access violation, como debe ser

Entonces creo que habra que hilar mas fino: Uso Delphi 2010 y estoy corriendo Windows 10 64 bits

En esas condiciones este codigo:

Código Delphi [-]
procedure TForm2.Button1Click(Sender: TObject);
var
  LocalFoo: TObject;
begin
  FormFoo.Free;

  LocalFoo.Free;
end;

Ejecuta sin problemas: lo que me causo tanta curiosidad es que el Button1 del form desaparecia...entonces, agregando el ShowMessage:

Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
var
  LocalFoo: TObject;
begin
  FormFoo.Free;
  ShowMessage(LocalFoo.ClassName);
  LocalFoo.Free;
end;

El mensaje imprime "TButton" :D

Mas extraño aun, puedo hacer esto sin problemas:

Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
var
  LocalFoo: TObject;
begin
  LocalFoo.Free;
  ShowMessage(LocalFoo.ClassName);
end;

No da error Access Violation!!! No puede ser!! Me imprime "TButton"!

Las variantes son infinitas:

Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
var
  LocalFoo: TObject;
begin
  LocalFoo.Free;
  ShowMessage(TButton(LocalFoo).Caption); // imprime "Button1"
end;

AgustinOrtu 21-12-2015 02:14:34

Código Delphi [-]
uses
  Rtti;

var
  X: Integer = 1;
  Y: Integer = 1;

procedure TForm1.Button1Click(Sender: TObject);
var
  LocalFoo: TObject;
  Ctx: TRttiContext;
  T: TRttiType;

  FooParentClassName: string;
  AClass: TClass;
  Button2: TButton;
begin
  LocalFoo.Free;
  FooParentClassName := LocalFoo.ClassParent.ClassName;
  AClass := LocalFoo.ClassType;
  T := Ctx.GetType(AClass);

  Button2 := T.GetMethod('Create').Invoke(AClass, [Self]).AsObject as TButton;
  Button2.Parent := Self;
  Button2.Top := Y * 10;
  Button2.Left := X * 25;
  Inc(X);

  if Button2.Left + Button2.Width >= Width then
  begin
    X := 1;
    Inc(Y, 4);
  end;

  Button2.Caption := FooParentClassName;
  Button2.OnClick := Button1Click;
end;

Este codigo crea un boton con caption "TCustomButton", lo coloca en el formulario, y destruye a Button1.
A su vez, asigno el mismo evento que puedo ir invocando una y otra vez y nunca se produce ninguna Access Violation

ecfisa 21-12-2015 03:27:29

Hola Agustin.
Cita:

Empezado por AgustinOrtu (Mensaje 500682)
...
Pero el problema son las variables locales; es verdad, de hecho tiene logica, que si yo declaro una variable, y no la inicializo nunca, el valor no esta definido; es decir que una llamada a Free deberia arrojar una excepcion Acess Violation. Bueno, curiosa fue mi sorpresa al correr el codigo anterior: no se elevo ninguna excepcion.

A mi modo de ver, LocalFoo.Free no genera una excepción por que cuando se declara una variable local (en la pila), Delphi no inicializa la referencia y tiene un valor que apunta a una dirección indeterminada, normalmente basura (aunque excepcionalmente podría ser nil).

Por otro lado el método TObject.Free está implementado así,
Código Delphi [-]
procedure TObject.Free;
begin
  if Self <> nil then Destroy;
end;
pero por ser LocalFoo una variable local y no estar inicializada, no tendrá el valor nil sino garbage y se llamará al método Destroy sobre la dirección del invocante (Sender en el caso).

Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
var
  LocalObj: TObject;
begin
  if LocalObj = Sender then ShowMessage('LocalObj referencia lo mismo que Sender');
  LocalObj.Free; // igual que: Sender.Free
end;

Es por eso que se insiste en inicializar las variables locales:
Código Delphi [-]
procedure TForm1.Button2Click(Sender: TObject);
var
  LocalObj: TObject;
begin
  LocalObj := nil;
  if LocalObj = Sender then ShowMessage('LocalObj es Sender'); // no se muestra el mensaje,
  LocalObj.Free;                                               // y nada raro sucede
end;
Hay casos como el tipo string donde no es necesaria la inicialización, aunque hacerlo no provoca perjuicio alguno.

Saludos :)

AgustinOrtu 21-12-2015 03:34:37

Yo creo que hay algo mas

No esta apuntando a garbage como decis: creo que por alguna feliz coincidencia, siempre apunta a Sender

Creo que tiene que ver con el codigo que emite el compilador; desgraciadamente, no soy capaz de leer lenguaje ensamblador


Código Delphi [-]
var
  Local: TObject;
begin
  Local.Free; // acesss violation aca
  ShowMessage(Local.ClassName);
  Button1.Visible := True; // comentando esta linea, desaparece el access violation
end;

ecfisa 21-12-2015 04:01:38

Hola Agustin.
Cita:

Empezado por AgustinOrtu (Mensaje 500690)
Yo creo que hay algo mas

No esta apuntando a garbage como decis: creo que por alguna feliz coincidencia, siempre apunta a Sender

...

No parece ser así en todos los casos, por ejemplo reduciendo el código a esto,
Código Delphi [-]
...
procedure TForm1.Button1Click(Sender: TObject);
var
  LocalObj: TObject;
begin
  LocalObj.Free;
end;

end.
cierra Form1 y deja el proyecto residente en memoria.

No sé con seguridad que valor tiene la variable LocalObj al momento de la llamada a Free, pero sin dudas que inicializando la variable local no se produce ninguna anomalía.

Saludos :)


La franja horaria es GMT +2. Ahora son las 23:24:39.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi