Wednesday, September 16, 2009

TRttiContext.Create() & TRttiContext.Free()

I thought I needed to take a break from the normal articles to explain why I call
TRttiContext.Create() and TRttiContext.Free() when in fact you don't need too.

Yes you don't need to do it, but yet I do... Why?

First off lets look at the implementation of both.


class function TRttiContext.Create: TRttiContext;
begin
Result.FContextToken := nil;
end;

procedure TRttiContext.Free;
begin
FContextToken := nil;
end;


At first glance there is nothing special, FContextToken is set to NIL in both cases.

So what does setting FContextToken to NIL really do?

We all know that Delphi currently does not have any Garbage collection mechanism. As such having a rich RTTI library that is based on Objects could be problematic.

In my code an I able to such cool things as...

c.GetType().GetField().FieldType.ToString

Without having to set temporary values for each returned object, to free them.
How is this possible without a garbage collector?

Well under the hood you will find TRttiPool that contains all of the RTTI object that are created.
When this pool is freed all of the objects created during RTTI calls are then freed.

The construction and destruction of this pool is controlled by TPoolToken which is the Interface that is stored in FContextToken.

When the TPoolToken is created and freed, the PoolRefCount is maintained, when it reaches zero the TRttiPool is freed.

So why call .Create() and .Free()

So lets start with .Create()

So it's not like calling this code is a huge overhead, yes sure a record is managed and as such it will be initialized, making the call to set FContextToken redundant. well sort of... Although I don't intend every create a pointer to TRttiContext, its possible have the memory space not be initialized as expected, unless you are calling .Create.

I also can't be sure that the implementation will remain as simple. It is very possible that additional code could be called at this point. Keeping my code safe in the future is always critical to me in my designs.

Moving on to .Free()

Well, I like to keep my memory foot print clean. Sure when the TRttiContext variable goes out of scope the exact same behavior will occur.

Very early on when playing with new RTTI I inadvertently caused an access violation in a TCustomAttribute Descendant destructor. With the call to .free() it was easier to see what had caused the problem. Instead of having it occur after the destruction of the Object that had the TRttiContext declared as a field.

And just to stop the obvious comment.... Yes, I know the documentation say's you don't have to call .Free() And implies that the technique is important because it insures that all RTTI Objects are cached and reused, well this would only be true of TRttiContext was declared as a global variable, which I think is a very BAD idea.

Instead I think you should keep your TRttiContext around as long as you need it, but free it when the time is correct.

In short... I find it a matter of personal preference.

7 comments:

  1. The problem I have with Free is that most experienced Delphi developers know that Free is a bad practice. Whenever we come across a Free, we're liable to change it to FreeAndNil, because long experience has shown us it's safer that way.

    If I'm understanding you right, TValue contains an interface reference. FreeAndNil on a TValue would therefore be a FreeAndNil on that interface, which won't generate a compiler error, warning, or even a hint, but will crash at runtime.

    So IMO, the Free is basically poison bait. It sits there, smelling up the code, and inviting anyone to fix it; but if they do...

    Thanks, but no thanks. I think I'll skip the Free.

    ReplyDelete
  2. > The problem I have with Free is that most experienced Delphi
    > developers know that Free is a bad practice

    And the other experienced Delphi developers know why, or should I better write, where not to use FreeAndNil.

    ReplyDelete
  3. FreeAnNil work with TObject and since TRttiContext and TValue are Records, it just not going to work at all.

    The comment in the code above FreeAndNil reads:

    { FreeAndNil frees the given TObject instance and sets the variable reference
    to nil. Be careful to only pass TObjects to this routine. }

    With methods on records you have to be careful to know what your working with. Replacing all calls to .Free with FreeAndNil is going to be more and more problematic.

    ReplyDelete
  4. Joe: I'd have to disagree. I rarely find a case where FreeAndNil is necessary. I prefer not to use it, because it's not type safe. Why it takes an untyped var parameter and not a "var TObject" is beyond me, but it does, and that makes FreeAndNil "poison bait," as you put it, because you can call it on things that aren't objects and corrupt data.

    Only in very unusual cases do I actually have to use FreeAndNil. Understanding the principle of object ownership usually makes it unnecessary.

    ReplyDelete
  5. TRttiContext was originally an object, and was changed to a record to get automatic lifetime management, but there was some concerns about unfamiliarity - worries that people wouldn't be sure how to create it and free it. So, the Create class function and Free method were added. It's true that the Free is somewhat misleading if you end up passing the context to FreeAndNil, but this is a tradeoff that's hard to manage. To sneak in a little editorial, people who use FreeAndNil indiscriminately are somewhat guilty of cargo cult programming, in my personal opinion.

    To answer Mason, FreeAndNil can't take a var TObject because var (and out) parameters are invariant with respect to the parameter type. If you had a field FList: TList, you wouldn't be able to pass it to FreeAndNil(var TObject) - polymorphism wouldn't work. This is because SomeMethod(var x: TObject) could assign e.g. TForm to the location x, and a caller who passed SomeMethod(FList) would be most surprised at the class type change.

    ReplyDelete
  6. Barry: Oh, OK. That makes sense. I don't suppose there would be some way in a later version to fix that? If you could put a class constraint on the parameter, like you can do with generics, to make sure that you can't pass in a non-TObject, that would make FreeAndNil safer...

    ReplyDelete
  7. @masonwheeler: FreeAndNil can't use (var TObject) as its signature, because then you can only pass it TObject type objects. Nobody creates those as they're useless. Passing any descendant type instances will make the compiler halt with an error. Since it's a var, you're required to pass a TObject, not a descendant.

    Consider:

    type
     TFoo=class(TObject)
     end;
     TBar=class(TFoo)
      FBar:Integer;
     end;

    procedure CreateFoo(var c:TFoo);
    begin
     c:=TFoo.Create;
    end;

    var bar:TBar;
    begin
     CreateFoo(bar);
     bar.FBar:=10; //this is a boo-boo
     //the created object is a TFoo, not a TBar
    end;


    This illustrates why the compiler will flag an error, and why FreeAndNil uses an untyped parameter.

    The type-safe way of freeing, then, is to write:

     Obj.Free;
     Obj:=nil;


    Unfortunately, since in Delhi objects are not automatically reference counted, this kind of thing may also cause AVs:

    var a,b:TObject;

    begin
     a:=TObject.Create;
     b:=a; //no reference is noted anywhere
     a.Free;
     a:=nil;

     //safe, so far.
     if Assigned(b) then
      ShowMessage(b.ToString); //AV, even though we checked for nil!

    ReplyDelete