This question is a continuance of a particular comment from people on stackoverflow which I've seen a few different times now. I, along with the developer who taught me Delphi, in order to keep things safe, have always put a check if assigned()
before freeing objects, and before doing other various things. However, I'm now told that I should not be adding this check. I'd like to know if there is any difference in how the application compiles/runs if I do this, or if it won't affect the result at all...
if assigned(SomeObject) then SomeObject.Free;
Let's say I have a form, and I'm creating a bitmap object in the background upon the form's creation, and freeing it when I'm done with it. Now I guess my problem is I got too used to putting this check on a lot of my code when I'm trying to access objects which might potentially have been free'd at some point. I've been using it even when it's not necessary. I like to be thorough...
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FBitmap: TBitmap;
public
function LoadBitmap(const Filename: String): Bool;
property Bitmap: TBitmap read FBitmap;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FBitmap:= TBitmap.Create;
LoadBitmap('C:\Some Sample Bitmap.bmp');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if assigned(FBitmap) then begin //<-----
//Do some routine to close file
FBitmap.Free;
end;
end;
function TForm1.LoadBitmap(const Filename: String): Bool;
var
EM: String;
function CheckFile: Bool;
begin
Result:= False;
//Check validity of file, return True if valid bitmap, etc.
end;
begin
Result:= False;
EM:= '';
if assigned(FBitmap) then begin //<-----
if FileExists(Filename) then begin
if CheckFile then begin
try
FBitmap.LoadFromFile(Filename);
except
on e: exception do begin
EM:= EM + 'Failure loading bitmap: ' + e.Message + #10;
end;
end;
end else begin
EM:= EM + 'Specified file is not a valid bitmap.' + #10;
end;
end else begin
EM:= EM + 'Specified filename does not exist.' + #10;
end;
end else begin
EM:= EM + 'Bitmap object is not assigned.' + #10;
end;
if EM <> '' then begin
raise Exception.Create('Failed to load bitmap: ' + #10 + EM);
end;
end;
end.
Now let's say I'm introducing a new custom list object called TMyList
of TMyListItem
. For each item in this list, of course I have to create/free each item object. There's a few different ways of creating an item, as well as a few different ways of destroying an item (Add/Delete being the most common). I'm sure it's a very good practice to put this protection here...
procedure TMyList.Delete(const Index: Integer);
var
I: TMyListItem;
begin
if (Index >= 0) and (Index < FItems.Count) then begin
I:= TMyListItem(FItems.Objects[Index]);
if assigned(I) then begin //<-----
if I <> nil then begin
I.DoSomethingBeforeFreeing('Some Param');
I.Free;
end;
end;
FItems.Delete(Index);
end else begin
raise Exception.Create('My object index out of bounds ('+IntToStr(Index)+')');
end;
end;
In many scenarios, at least I would hope that the object is still created before I try to free it. But you never know what slips might happen in the future where an object gets free'd before it's supposed to. I've always used this check, but now I'm being told I shouldn't, and I still don't understand why.
EDIT
Here's an example to try to explain to you why I have a habit of doing this:
procedure TForm1.FormDestroy(Sender: TObject);
begin
SomeCreatedObject.Free;
if SomeCreatedObject = nil then
ShowMessage('Object is nil')
else
ShowMessage('Object is not nil');
end;
My point is that if SomeCreatedObject <> nil
is not the same as if Assigned(SomeCreatedObject)
because after freeing SomeCreatedObject
, it does not evaluate to nil
. So both checks should be necessary.
This is a very broad question with many different angles.
The meaning of the Assigned
function
Much of the code in your question betrays an incorrect understanding of the Assigned
function. The documentation states this:
Tests for a nil (unassigned) pointer or procedural variable.
Use
Assigned to determine whether the pointer or procedure referenced by P
is nil. P must be a variable reference of a pointer or procedural
type. Assigned(P) corresponds to the test P<> nil for a pointer
variable, and @P <> nil for a procedural variable.
Assigned returns
False if P is nil, True otherwise.
Note: Assigned cannot detect a
dangling pointer--that is, one that is not nil but no longer points to
valid data. For example, in the code example for Assigned, Assigned
does not detect that P is not valid.
The key points to take from this are:
Assigned
is equivalent to testing <> nil
.
Assigned
cannot detect whether the pointer or object reference is valid or not.
What this means in the context of this question is that
if obj<>nil
and
if Assigned(obj)
are completely interchangeable.
Testing Assigned
before calling Free
The implementation of TObject.Free
is very special.
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;
This allows you to call Free
on an object reference that is nil
and doing so has no effect. For what it is worth, I am aware of no other place in the RTL/VCL where such a trick is used.
The reason why you would want to allow Free
to be called on a nil
object reference stems from the way constructors and destructors operate in Delphi.
When an exception is raised in a constructor, the destructor is called. This is done in order to deallocate any resources that were allocated in that part of the constructor that succeeded. If Free
was not implemented as it is then destructors would have to look like this:
if obj1 <> nil then
obj1.Free;
if obj2 <> nil then
obj2.Free;
if obj3 <> nil then
obj3.Free;
....
The next piece of the jigsaw is that Delphi constructors initialise the instance memory to zero. This means that any unassigned object reference fields are nil
.
Put this all together and the destructor code now becomes
obj1.Free;
obj2.Free;
obj3.Free;
....
You should choose the latter option because it is much more readable.
There is one scenario where you need to test if the reference is assigned in a destructor. If you need to call any method on the object before destroying it then clearly you must guard against the possibility of it being nil
. So this code would run the risk of an AV if it appeared in a destructor:
FSettings.Save;
FSettings.Free;
Instead you write
if Assigned(FSettings) then
begin
FSettings.Save;
FSettings.Free;
end;
Testing Assigned
outside a destructor
You also talk about writing defensive code outside a destructor. For example:
constructor TMyObject.Create;
begin
inherited;
FSettings := TSettings.Create;
end;
destructor TMyObject.Destroy;
begin
FSettings.Free;
inherited;
end;
procedure TMyObject.Update;
begin
if Assigned(FSettings) then
FSettings.Update;
end;
In this situation there is again no need to test Assigned
in TMyObject.Update
. The reason being that you simply cannot call TMyObject.Update
unless the constructor of TMyObject
succeeded. And if the constructor of TMyObject
succeeded then you know for sure that FSettings
was assigned. So again you make your code much less readable and harder to maintain by putting in spurious calls to Assigned
.
There is a scenario where you need to write if Assigned
and that is where the existence of the object in question is optional. For example
constructor TMyObject.Create(UseLogging: Boolean);
begin
inherited Create;
if UseLogging then
FLogger := TLogger.Create;
end;
destructor TMyObject.Destroy;
begin
FLogger.Free;
inherited;
end;
procedure TMyObject.FlushLog;
begin
if Assigned(FLogger) then
FLogger.Flush;
end;
In this scenario the class supports two modes of operation, with and without logging. The decision is taken at construction time and any methods which refer to the logging object must test for its existence.
This not uncommon form of code makes it even more important that you don't use spurious calls to Assigned
for non-optional objects. When you see if Assigned(FLogger)
in code that should be a clear indication to you that the class can operate normally with FLogger
not in existence. If you spray gratuitous calls to Assigned
around your code then you lose the ability to tell at a glance whether or not an object should always exist.
Free
has some special logic: it checks to see whether Self
is nil
, and if so, it returns without doing anything -- so you can safely call X.Free
even if X
is nil
. This is important when you're writing destructors -- David has more details in his answer.
You can look at the source code for Free
to see how it works. I don't have the Delphi source handy, but it's something like this:
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;
Or, if you prefer, you could think of it as the equivalent code using Assigned
:
procedure TObject.Free;
begin
if Assigned(Self) then
Destroy;
end;
You can write your own methods that check for if Self <> nil
, as long as they're static (i.e., not virtual
or dynamic
) instance methods (thanks to David Heffernan for the documentation link). But in the Delphi library, Free
is the only method I know of that uses this trick.
So you don't need to check to see if the variable is Assigned
before you call Free
; it already does that for you. That's actually why the recommendation is to call Free
rather than calling Destroy
directly: if you called Destroy on a nil
reference, you would get an access violation.
Why you shouldn't call
if Assigned(SomeObject) then
SomeObject.Free;
Simply because you would execute something like this
if Assigned(SomeObject) then
if Assigned(SomeObject) then
SomeObject.Destroy;
If you call just SomeObject.Free;
then it's just
if Assigned(SomeObject) then
SomeObject.Destroy;
To your update, if you afraid of the object instance reference use FreeAndNil. It will destroy and dereference your object
FreeAndNil(SomeObject);
It's similar as if you call
SomeObject.Free;
SomeObject := nil;
I'm not completely sure of that, but it seems:
if assigned(object.owner) then object.free
works fine. In this example it would be
if assigned(FBitmap.owner) then FBitmap.free