01.05.2022, Vladimír Klaus, navštíveno 754x

Delphi

K napsání tohoto malého článku mě přivedla osobní zkušenost, přesněji řečeno vlastní chyba, která mě stála zcela zbytečné 2hodinové pátrání po příčinách problému. A protože problém byl ten, že při debuggování vytuhlo Delphi, bylo to o to zábavnější. Vše se ještě zkomplikovalo instalací patche, na který jsem to z počátku sváděl. ;-)

Začnu tím, co se nemá moc používat, ale v malých jednoduchých situacích proti tomu nelze nic namítat. Je to použití with, jako např. zde:

with TMyObject.Create(nil) do begin
  ProvedAkci;
  Free;
end;

Toto zcela spolehlivě funguje, ale do neřešitelné situace se dostanete, pokud chcete onen vytvářený objekt použít - odkázat se na něj:

with TMyObject.Create(nil) do begin
  ProvedAkci(...odkaz na instanci objektu...);
  Free;
end;

To v podstatě nejde, nebo se dělá nějak oklikou, což se mi nechtělo podstupovat. Přepsal jsem to tedy do klasické, jen trochu ukecanější varianty. A samozřejmě nešlo o přepisování, ale kopírování kusů kódu.

var obj:=TMyObject.Create(nil);
try
  ProvedAkci(obj);
finally
  Free;
end;

Kompilátor nic nenamítal, jen mi to "náhodně" způsobovalo vytuhnutí Delphi. To "náhodné" se později vyjasnilo na opakované, resp. již druhé použití. Samozřejmě jsem mezitím vyzkoušel desítky věcí, přepisování, změnu názvů, vypínání optimalizace, restartů Delphi, odinstalování patche apod.

Delphi - Proč se vyhnout používání with a jak je to s Free resp. FreeAndNil, obr. 1

Když se znovu podíváte na uvedený kód, je zřejmé, že moje "Free" neuvolňuje "obj", ale objekt, ze kterého je toto voláno. Takže první volání projde, pak už je to průšvih. Je s podivem, že s tím mělo Delphi takové problémy, ukazovalo mi, že odkaz na ten nadřízený objekt je nedostupný (ale ne nil), tak jsem si říkal, že to musí být nějaká interní podivnost a ne takto triviální chyba.

Delphi - Proč se vyhnout používání with a jak je to s Free resp. FreeAndNil, obr. 2

Je jasné, že když objekt vytváříte v nějaké proceduře, kde ho na konci uvolňujete, stačí použít Free, protože stejně nebude odnikud dostupný, ale tady by se sakra vyplatilo to udělat klasicky! Stačí důsledně všude používat "FreeAndNil" a k problému by vůbec nedošlo, protože do funkce musíte dát odkaz na (správný) objekt, navíc dojde k nastavení nil, což se dá později někde odchytit.

var obj:=TMyObject.Create(nil);
try
  ProvedAkci(obj);
finally
  Free;              // nikdy nepoužívejte, tedy vyhněte se konstrukci "with TMyObject.Create"
  obj.Free;          // bez problému, ale jen pokud není obj = nil
  FreeAndNil(obj);   // zcela bez problému za jakýchkoli okolností
end;

Další smůlou bylo, že jsem se stále snažil debuggovat, místo, abych aplikaci spustil normálně. Místo vytuhávání Delphi by hned spadla a bylo by jasné, že jde o nějakou chybu v kódu, která by se dala začít řešit - zvláště při použití třeba madExceptu, který ukáže, že k AccessViolation došlo při TObject.Free.

Delphi - Proč se vyhnout používání with a jak je to s Free resp. FreeAndNil, obr. 3

Stále je ale pro mě záhadou, proč taková záležitost odrovnala celé Delphi, resp. Debugger.