-
-
Notifications
You must be signed in to change notification settings - Fork 45
Always use FreeAndNil
We advise to always (or at least "as much as reasonably possible") free your object instances using FreeAndNil(Obj).
- Not
Obj.Free - Not
Obj.Destroy - Not
Obj.Destroy; Obj := nil.
This entire page applies equally to both Delphi or FPC (just as most CGE and Michalis Kamburelis text).
Obj.Free calls the destructor, only if Obj is not nil.
See https://castle-engine.io/modern_pascal#_how_to_free .
Note that Obj.Free doesn't set Obj to nil. It cannot -- it doesn't even have the address of variable Obj. And it can be used in contexts like Foo.SomeFunctionThatReturnsObject(SomeParam).Free where such variable doesn't exist.
Free implementation is just doing
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;FreeAndNil(Obj) calls the destructor, only if Obj is not nil, and moreover it makes sure the Obj is set to nil.
For starters, you can think it's implemented like this:
// This is not a real implementation of FreeAndNil! It's a simplification, see below.
procedure FreeAndNil(var A: TObject);
begin
if A <> nil then
begin
A.Destroy;
A := nil;
end;
end;We advise to always use FreeAndNil.
Notes:
-
Using
FreeAndNilis naturally not a guarantee that all references to this object areniled (for this you need to use free notification, https://castle-engine.io/modern_pascal#_free_notification ) but it's at least a bit better (at least that one reference it set tonil). -
FreeAndNilalso does a trick to set variable tonilbefore calling the destructor (for this, it saves the reference to a local variable before niling the variable) which is beneficial in some edge-cases (it means the variable isnilalready in the middle of the destructor, which is a bit safer and matters in case things get complicated with callbacks or virtual methods that could look at that variable). This is another reason why our above code snippet was only "over-simplification for illustration". It's actually sthg like:procedure FreeAndNil(var A: TObject); var Tmp: TObject; begin if A <> nil then begin Tmp := A; A := nil; Tmp.Destroy; end; end;
-
The extra work done by
FreeAndNilshall never impact your speed in practice (both CGE and your user code do thousands of things that affect speed more than additional setting of pointer-sized variable to zero :) ). -
The real implementation of
FreeAndNilin compilers has to treat the parameter differently.- It has to be done differently, to allow any
TObjectdescendant for theAparameter, not only exactlyTObject, as would be required byvar A: TObject. - FPC seems to do it a bit differently than Delphi. FPC allows any type, actually, so it's untyped parameter,
FreeAndNil(var A). Delphi seems to employ special compiler logic to makeFreeAndNilcorrect and only allowTObject.
- It has to be done differently, to allow any
But my variable Obj is always a valid pointer, my code already assumes it, why would I bother setting it to nil?
I advise to use FreeAndNil(Obj), not Obj.Free, even if you think the variable should always be a valid pointer.
Because you may be wrong :), I give below examples how one can be easily mistaken about it.
And a nil is better than a dangling pointer:
-
It is always easier to notice in the debugger /
Writeln/WritelnLogetc. Any way you look atObjvariable, it will clearly benil, not0x1234567. -
Accessing
nil(get/set a field of it, call a virtual method of it) will 100% reliably make access violation at the relevant line.In contrast, accessing an invalid (dangling) non-nil pointer is undefined -- maybe it will crash, maybe not (and then
obj.MyFieldmay read a memory garbage, setting that field may modify an unpredictable other variable, maybe it was freed + created with exact some pointer (it can often happen in some code) and then it will somewhat behave like it is OK).
destructor TXxx.Destroy;
begin
Obj.Free;
SomeOtherFinalizationMethod;
inherited;
end;
procedure TXxx.SomeOtherFinalizationMethod;
begin
Obj.MyField.Free;
end;This above is an incorrect code. But it may be hard to notice. Esp. if you imagine this is the end result of 10 refactors over 10 years :), and the real-life example would have much longer implementations of both Destroy and SomeOtherFinalizationMethod.
The Obj.MyField.Free call above is invalid, it will access Obj.MyField when Obj is an invalid pointer. But maybe it will sometimes work, in case that memory still happens to be accessible and not allocated to something else.
Note:
-
The goal (with using
FreeAndNil(Obj)) is not to necessarily force you to doif Obj <> nil thenchecks before calling all methods of it. I'm not saying you should changeSomeOtherFinalizationMethodimplementation to checkif Obj <> nil then. It is OK to assume thatObjis notnilsometimes. CGE does it too, see https://castle-engine.io/coding_conventions#nil -- we even encourage to assume, by default, that things "are never nil" (and only account fornilwhen explicitly documented it can happen). It is certainly more readable to be able to assume "Obj is not nil" than to safeguard all code withObj <> nilchecks. -
The goal is to make it easier to debug, in case your assumption goes wrong. E.g. in above case, if the destructor would do
FreeAndNil(Obj), then you would immediately see the problem (100% reliable crash insideSomeOtherFinalizationMethodat line accessingObj.MyField), and likely conclude "oh, I need to callSomeOtherFinalizationMethodearlier".
Another example with edge-case:
procedure TXxx.Recreate;
begin
Obj.Free;
Obj := TSomeObject.Create;
end;
destructor TXxx.Destroy;
begin
Obj.Free:
inherited;
end;The above code is a mistake -- because if TSomeObject constructor will raise an exception, then it will cause (maybe!) another exception from TXxx.Destroy that accesses an invalid pointer at finalization. So you would "mask" one, real, exception (in TSomeObject constructor) with another (in TXxx destructor). When your code crashes, when user submits a bug with a stack trace, you probably wish it will say something like "TSomeObject constructor crashed", not "TXxx.Destroy destructor crashed".
Using FreeAndNil(Obj) always, in both occurrences above instead of Obj.Free, would avoid it. This way there is no "window" inside TXxx.Recreate execution when the Obj is invalid pointer.