5
votes

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?

4
You should rename your method Free! There is already declared such a method in TObject. It would be far better to override Destroy and call inherited inside.Uwe Raabe
@Uwe: yeah, that was a poor, poor choice of name. :) I'd rather let it stand as it is, though, due to the numerous comment/answers from yourself and others. Thanks!conciliator
Simple answer: objects can not be niled, only references to objects can :)mjn
Simpler code example; A := B; B := nil; Note that only B is now nil, not necessarily A.Warren P

4 Answers

14
votes

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;
15
votes

MyObject is a different variable from the field FMyObject. And you're only niling the field FMyObject.

FreeAndNil frees to object pointed to, and nils 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.

8
votes

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.

1
votes

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.