delphipolymorphismtypecasting-operatortypecast-operator

Is there a way to dynamically type cast by class information parameter in Delphi?


I am having some difficulty understanding typecasting when using a class that is a passed parameter. I tried searching for this but couldn't find other answers.

I am working with some legacy Delphi code, using Delphi 2006, which doesn't support Generics (introduced in Delphi 2009).

The code is using TLists to store pointers to instantiated classes of particular types. When clearing the list, they use this:

  procedure ClearList(AList: TList);  
  var i: Integer;
  begin
    for i := 0 to AList.Count - 1 do
      TObject(AList[i]).Free;
    AList.Clear;
  end; 

And it is called like this:

  ClearList(FExtraVisitTypes);
  ClearList(FDiagnoses);
  ClearList(FProcedures);
  ClearList(FImmunizations);
  ClearList(FSkinTests);
  ClearList(FPatientEds);
  ClearList(FHealthFactors);
  ClearList(FExams);

My understanding of this may be off, but I am concerned that if the pointed-to objects are freed as TObject, that the destructor of the descendant object won't be called, potentially leading to a memory leak. (My polymorphisim kung-fu is a bit rusty, which may be causing my confusion.)

So I tried to change the clear function as below:

 procedure ClearList(AList: TList; ItemClass: TPCEItemClass);  //mod to add ItemClass
  var i: Integer;
  begin
    for i := 0 to AList.Count - 1 do begin
      (AList[i] as ItemClass).Free;
    end;
    AList.Clear;
  end;

TPCEItemClass is defined like this:

  TPCEItemClass = class of TPCEItem;

I then changed the clear calls like this:

  ClearList(FExtraVisitTypes, TPCEProc);
  ClearList(FDiagnoses, TPCEDiag);
  ClearList(FProcedures, TPCEProc);
  ClearList(FImmunizations, TPCEImm);
  ClearList(FSkinTests, TPCESkin);
  ClearList(FPatientEds, TPCEPat);
  ClearList(FHealthFactors, TPCEHealth);
  ClearList(FExams, TPCEExams);

But the compiler won't allow this and gives this error:

[Pascal Error] uPCE.pas(1730): E2015 Operator not applicable to this operand type

For this erroneous line:

  (AList[i] as ItemClass).Free;

Questions:

  1. Does the original way of coding, where the item is freed by simply calling the great-great-great (etc) ancestor Free method end up effecting the descendant's destructor method? As I write this, I'm now thinking that it actually does. But I don't know why. So any answers to help me keep this in my head would be great.

  2. Why does my method of trying to typecast via the parameter which is of type class not work? Is this just not allowed? Or is my syntax wrong? Is there another way to do this?

  3. Am I going about this all wrong? Is there a better way?

Thanks


Solution

  • I am concerned that if the pointed-to objects are freed as TObject, that the destructor of the descendant object won't be called, potentially leading to a memory leak.

    That is not the case for classes that are properly implemented.

    All classes derive from TObject. TObject.Free() calls the TObject.Destroy() destructor, which is virtual. Any descendant that requires destruction logic must override that destructor (if it doesn't, it has a flaw that needs fixing).

    So, in properly written code, the original code will work perfectly fine as shown. Calling Free() on any valid and correctly implemented object will invoke its most-derived destructor.

    Now, that being said, there have been plenty of cases over the years of people forgetting to override the destructor when their classes require it, thus causing the kinds of memory leaks you are worried about. So, make sure you pay attention to what your classes are doing, and you will be fine.

    So I tried to change the clear function as below ... But the compiler won't allow this and gives this error

    Correct, because you can't perform a type-cast on an object using a variable to a metaclass type, like you are trying to do. Type-casts require the target type to be specified at compile-time, but metaclass variables are not assigned until runtime.

    Does the original way of coding, where the item is freed by simply calling the great-great-great (etc) ancestor Free method end up effecting the descendant's destructor method?

    The original code will work just fine 99% of the time, yes. Most Delphi coders are good about override'ing the destructor when it is appropriate. But that other 1% is only when you are dealing with classes that are not implemented correctly, in which case it is their author's responsibility to fix them, not your responsibility to fix the code that is calling Free() on them.

    As I write this, I'm now thinking that it actually does. But I don't know why.

    Polymorphic dispatch of the virtual destructor, just like when calling any other virtual method.

    Why does my method of trying to typecast via the parameter which is of type class not work? Is this just not allowed?

    Correct. It is illegal.

    Is there another way to do this?

    No (well, yes, but it involves walking an object's class structure's manually at runtime, but that requires a deep understanding of how the compiler lays out objects in memory, so I'm not going to get into that here).