-
-
Notifications
You must be signed in to change notification settings - Fork 41
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
.
I advise to always use FreeAndNil
.
Notes:
-
Using
FreeAndNil
is naturally not a guarantee that all references to this object arenil
ed (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
). -
FreeAndNil
also does a trick to set variable tonil
before 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 isnil
already 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). -
The extra work done by
FreeAndNil
shall 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 :) ).
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
/WritelnLog
etc. Any way you look atObj
variable, 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.MyField
may 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 then
checks before calling all methods of it. I'm not saying you should changeSomeOtherFinalizationMethod
implementation to checkif Obj <> nil then
. It is OK to assume thatObj
is notnil
sometimes. 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 fornil
when 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 <> nil
checks. -
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 insideSomeOtherFinalizationMethod
at line accessingObj.MyField
), and likely conclude "oh, I need to callSomeOtherFinalizationMethod
earlier".
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.