Reputation: 6138
I want to pass an object A to a second object B, have B do some processing and finally release A in case it's not needed anymore. A watered down version is given below.
program Project6;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyObject = class(TObject)
public
FField1: string;
FField2: string;
end;
TBigObject = class(TObject)
public
FMyObject: TMyObject;
procedure Bind(var MyObject: TMyObject);
procedure Free();
end;
procedure TBigObject.Bind(var MyObject: TMyObject);
begin
FMyObject := MyObject;
end;
procedure TBigObject.Free;
begin
FreeAndNil(FMyObject);
Destroy();
end;
var
MyObject: TMyObject;
BigObject: TBigObject;
begin
try
MyObject := TMyObject.Create();
BigObject := TBigObject.Create();
BigObject.Bind(MyObject);
BigObject.Free();
if (Assigned(MyObject)) then begin
WriteLn('Set MyObject free!');
MyObject.Free();
end;
ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
(Never mind the awful design.) Now, what I don't understand is why FreeAndNil actually does free MyObject, yet Assigned(MyObject)
is evaluated to true (giving an AV at MyObject.Free()
).
Could someone please help enlighten me?
Upvotes: 6
Views: 2206
Reputation: 28806
There are a few peculiarities in your code.
First, you should not re-write Free, you should override the virtual destructor (Destroy) of your class.
But ISTM that BigObject is not the owner of MyObject, so BigObject should not try to free it at all.
As CodeInChaos already said, FreeAndNil only frees one variable, in this case the FMyObject field. FreeAndNil is not required anyway, since nothing can happen after the object was freed.
Assigned can't be used to check if an object was freed already. It can only check for nil, and FreeAndNil only sets one reference to nil, not the object itself (this is impossible).
Your program design should be thus, that an object can and will only be freed if nothing is accessing it anymore.
Upvotes: 2
Reputation: 612794
You have two copies of the reference to the object but are only setting one of them to nil. Your code is equivalent to this:
i := 1;
j := i;
i := 0;
Writeln(j);//outputs 1
I'm using integers in this example because I'm sure you are familiar with how they work. Object references, which are really just pointers, behave in exactly the same way.
Casting the example in terms of object references makes it look like this:
obj1 := TObject.Create;
obj2 := obj1;
obj1.Free;//these two lines are
obj1 := nil;//equivalent to FreeAndNil
//but obj2 still refers to the destroyed object
Aside: You should never call Destroy directly and never declare a method called Free. Instead override Destroy and call the static Free defined in TObject, or indeed FreeAndNil.
Upvotes: 9
Reputation: 108790
MyObject
is a different variable from the field FMyObject
. And you're only nil
ing the field FMyObject
.
FreeAndNil
frees to object pointed to, and nil
s the variable you passed in. It doesn't magically discover and nil
all other variables that point to the object you freed.
FreeAndNil(FMyObject);
does the same thing as:
object(FMyObject).Free();
FMyObject=nil;
(Technically this is not entirely correct, the cast to object is a reinterpret cast due to the untyped var
parameter, but that's not relevant here)
And that obviously only modifies FMyObject
and not MyObject
Oh I just noticed that you're hiding the original Free
method? That's insane. FreeAndNil
still uses the original Free
. That doesn't hit you in your example because you call Free
on a variable with the static type TBigObject
and not FreeAndNil
. But it's a receipt for disaster.
You should instead override the destructor Destroy
.
Upvotes: 16
Reputation: 37211
The reason is simple, you nil one reference but not the other. Consider this example:
var
Obj1, Obj2: TObject;
begin
Obj1 := TObject.Create;
Obj2 := Obj1;
FreeAndNil(Obj1);
// Obj1 is released and nil, Obj2 is non-nil but now points to undefined memory
// ie. accessing it will cause access violations
end;
Upvotes: 15