Riporto un argomento discusso in un forum internazionale di Delphi https://en.delphipraxis.net/topic/6536-why-isnt-this-dangerous-or-is-it/ (https://en.delphipraxis.net/topic/6536-why-isnt-this-dangerous-or-is-it/), argomento che sembra idiota ma non lo è assolutamente. L'ho portato su Lazarus e l'effetto è lo stesso.
Parto dal fatto che io non mi sarei mai sognato di fare una cosa simile, però effettivamente qualcuno potrebbe pensare che una cosa simile sia effettivamente conveniente, MA NON LO E' ... NON E' DA FARSI.
Questo il codice, una FORM con un pulsante e una nuova classe definita:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
type
TDoSomething = class
Danger: string;
constructor create;
procedure FaiQualcosa;
end;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
public
end;
var
Form1: TForm1;
DoSomething: TDoSomething;
implementation
{$R *.lfm}
constructor TDoSomething.create;
begin
Danger := 'Danger!';
end;
procedure TDoSomething.FaiQualcosa;
begin
ShowMessage('Faccio qualcosa');
Sleep(1000);
ShowMessage('Ho finito di fare qualcosa');
Free; //<----- MAI FARE UNA COSA SIMILE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DoSomething.FaiQualcosa;
if Assigned(DoSomething) then // Questo vale TRUE perchè il puntatore contiene il vecchio valore, che punta però a una zona orami "liberata" di memoria ........
ShowMessage(DoSomething.Danger); //<------ ERRORE ???????
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DoSomething := TDoSomething.create;
end;
end.
In uno dei metodi della nuova classe è stato inserito il metodo FREE, con l'intento di "liberare" risorse della classe, ma ciò ha effetti deleteri.
Se eseguite quanto sopra, scoprirete che tutto viene eseguito ... più o meno correttamente senza evidenti errori ... salvo che l'ultimo ShowMessage che dovrebbe stampare la scritta "Danger!" stampa in realtà un riga vuota.
Anzi volendo proprio dire, LI NON DOVEVA ESSERE ESEGUITO NULLA O ANDARE IN ERRORE IL PROGRAMMA.
Cosa succede: la procedura FaiQualcosa esegue il "FREE" dell'istanza della classe, ma il puntatore, ossia la variabile "DoSomething" mantiene ancora il valore dell'istanza in quanto l'istanza non "appartiene" a se stessa. Il contesto di creazione dell'istanza di TDoSomething non è l'isatnaza stessa e quindi l'istanza non può "liberarsi" da sola. In effetti io non sono convinto che il FREE esegua ciò che si pensa, in quanto alcune funzionalità legate alla creazione di un oggetto potrebbero non appartenere all'istanza.
Esempio banale è un ActiveX (oggetto OLE), è un oggetto che si "crea" ma lui non può in alcun modo "autodistruggersi" perchè il controllo e la sua "vita" dipende dal sistema operativo e da questo deve essere "distrutto".
Scusate se uso termini non appropriati all'informatica, ma è per enfatizzare il tutto.
L'istanza della classe TDoSomething, il cui puntatore viene mantenuto in DoSomeThing, non esiste (l'istanza è stata "liberata") ma il suo puntatore esiste ancora e non è NIL. Quindi tutto presegue come nulla fosse. Con un codice più complesso, se invece di uno ShowMessage con visualizzato un "vuoto" avessimo chiamato un metodo che esegue qualcosa magari su un DB o peggio .........
Se si imposta "usa la unit heaptrc" nelle opzioni di progetto, viene generato un errore a runtime in queste condizioni, ma basta cambiare leggeremente codice e neanche quel flag consente di intercettare il malfunzionamento.
Condizioni di questo tipo sono difficilissime da debugare, sopratutto dopo qualche mese dalla stesura del codice. Ricordatevi le regole di buona programmazione, tra cui "chi crea un oggetto anche lo distrugge". In questo caso la Form1 lo crea e la Form1 (e solo la Form1) deve distruggerlo.
Ciao
argomento interessante, anche perché serve a chiarire meglio alcuni funzionamenti della programmazione ad oggetti
vorrei fare un paio di considerazioni su quelle penso siano buone pratiche di programmazione, proprio per evitare di addentrarsi in anfratti di questo genere :)
ok per il puntatore che punta ad una istanza che non esiste più, però guardando questo brano di codice
mi viene in mente che si è voluto forzare il comportamento con un errore nella scrittura del codice
procedure TForm1.Button1Click(Sender: TObject);
begin
DoSomething.FaiQualcosa;
if Assigned(DoSomething) then // Questo vale TRUE perchè il puntatore contiene il vecchio valore, che punta però a una zona orami "liberata" di memoria ........
ShowMessage(DoSomething.Danger); //<------ ERRORE ???????
end;
andrebbe chiaramente scritto in questo modo
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(DoSomething) then begin
DoSomething.FaiQualcosa;
ShowMessage(DoSomething.Danger);
end;
end;
non cambia chiaramente nulla, ma il test con Assigned va fatto prima di usare l'istanza :)
detto questo, che un'istanza si autodistrugga non è una buona pratica di programmazione in generale in quanto sicuramente non si è autocreata... bisognerebbe demandare al creatore la distruzione delle istanze
distruzione che sarebbe meglio fare sempre con FreeAndNil
nomorelogic
Edit:
FreeAndNil, serve appunto ad impostare il puntatore a nil dopo aver chiamato il destructor
https://wiki.freepascal.org/FreeAndNil