Home > Enterprise >  How to make Delphi call correct constructor during dynamic creating?
How to make Delphi call correct constructor during dynamic creating?

Time:02-06

I'm having problems with my Delphi 2006 seeming to call the incorrect constructor during dynamic creation.

I asked almost the exact same question 5 yrs ago (Why does Delphi call incorrect constructor during dynamic object creation?), and I have reviewed that. But that thread had issues of overriding virtual calls which I don't have now. I have also tried searching through StackOverflow for a matching question, but couldn't find an answer.

I am working with legacy code, so I didn't write much of this. (If you see comments below with '//kt' adding something, that is me).

The code has base class, TPCEItem as follow. Note that it does NOT have a constructor.

  TPCEItem = class(TObject)
  {base class for PCE items}
  private
    <irrelevent stuff>
  public
    <irrelevent stuff>
  end;

Next, there is class type to use for passing a parameter (more below).

  TPCEItemClass = class of TPCEItem;

Next I have a child class as follows. Note that it DOES have a contructor. The compiler will not allow me to add 'override' to this create method because the ancestor class where this is declared (TObject) does not define it as virtual.

  TPCEProc = class(TPCEItem)
  {class for procedures}
  protected
    <irrelevent stuff>
  public
    <irrelevent stuff>
    constructor Create;
    destructor Destroy; override;
  end;

The code then has a function for copying data, which is a conglomeration of descendant types. Because this is older code, mosts of these lists are plain TLists or TStringLists, holding untyped pointers. Thus for each copy command a corresponding type is passed in for correct use.

procedure TPCEData.CopyPCEData(Dest: TPCEData);
begin
  Dest.Clear;

  <irrelevent stuff>

  CopyPCEItems(FVisitTypesList,  Dest.FVisitTypesList,  TPCEProc); //kt added
  CopyPCEItems(FDiagnoses,       Dest.FDiagnoses,       TPCEDiag);
  CopyPCEItems(FProcedures,      Dest.FProcedures,      TPCEProc);
  CopyPCEItems(FImmunizations,   Dest.FImmunizations,   TPCEImm);
  CopyPCEItems(FSkinTests,       Dest.FSkinTests,       TPCESkin);
  CopyPCEItems(FPatientEds,      Dest.FPatientEds,      TPCEPat);
  CopyPCEItems(FHealthFactors,   Dest.FHealthFactors,   TPCEHealth);
  CopyPCEItems(FExams,           Dest.FExams,           TPCEExams);

  <irrelevent stuff>
end;

This CopyPCEItems is as follows:

procedure TPCEData.CopyPCEItems(Src: TList; Dest: TObject; ItemClass: TPCEItemClass);
var
  AItem: TPCEItem;
  i: Integer;
  IsStrings: boolean;
  Obj : TObject;

begin
  if (Dest is TStrings) then begin
    IsStrings := TRUE
  end else if (Dest is TList) then begin
    IsStrings := FALSE
  end else begin
    exit;
  end;
  for i := 0 to Src.Count - 1 do begin
    Obj := TObject(Src[i]);
    if(not TPCEItem(Src[i]).FDelete) then begin
      AItem := ItemClass.Create;  //<--- THE PROBLEMATIC LINE
      if (Obj.ClassType = TPCEProc) and (ItemClass = TPCEProc) then begin  //kt added if block and sub block below
        TPCEProc(Obj).CopyProc(TPCEProc(AItem));
      end else begin
        AItem.Assign(TPCEItem(Src[i]));  //kt <-- originally this line was by itself.
      end;
      if (IsStrings) then begin
        TStrings(Dest).AddObject(AItem.ItemStr, AItem)
      end else begin
        TList(Dest).Add(AItem);
      end;
    end;
  end;
end;

The problematic line is as below:

      AItem := ItemClass.Create;

When I step through the code with the debugger, and stop on this line, an inspection of the variable ItemClass is as follows

  ItemClass  = TPCEProc

The problems is that the .Create is calling TObject.Create, not TPCEProc.Create, which doesn't give me an opportunity to instantiate some needed TStringLists, and later leads to access violation error.

Can anyone help me understand what is going on here? I have a suspicion that the problem is with this line:

TPCEItemClass = class of TPCEItem;

It is because this is of a class of an ancestor type (i.e. TPCEItem), that it doesn't properly carry the information for the child type (TPCEProc)?? But if this is true, then why does the debugger show that ItemClass = TPCEProc??

How can I effect a call to TPCEProc.Create?

I have been programming in Delphi for at least 30 yrs, and it frustrates me that I keep having problems with polymorphism. I have read about this repeatedly. But I keep hitting walls.

Thanks in advance.

CodePudding user response:

When you are constructing objects through meta-class you need to mark its base class constructor as virtual, and if you need a constructor in any of the descendant classes they need to override that virtual constructor.

If the base class does not have a constructor, you will need to add empty one.

  TPCEItem = class(TObject)
  public
    constructor Create; virtual;
  end;

  TPCEItemClass = class of TPCEItem;

  TPCEProc = class(TPCEItem)
  public
    constructor Create; override;
    destructor Destroy; override;
  end;


constructor TPCEItem.Create;
begin
  // if the descendant class is TObject
  // or any other class that has empty constructor
  // you can omit inherited call
  inherited;
end;
  

CodePudding user response:

Congratulations you have identified the problematic line

AItem := ItemClass.Create;  //<--- THE PROBLEMATIC LINE

But what is wrong with this line? You are calling constructor method from existing class instance. You should not do this ever. You should only call constructor methods from specific class types not existing class instances.

So in order to fix your code change the mentioned line to

AItem := TPCEItem.Create;

You may be thinking of perhaps calling AItem := TPCEItemClass.Create; since above in your code you made next declaration

TPCEItemClass = class of TPCEItem; 

This declaration does not meant that TPCEItemClass is the same type as TPCEItem but instead that both types have same type structure but they are in fact two distinct types.


By the way what is the purpose of ItemClass: TPCEItemClass parameter of your CopyPCEItems procedure if you are not even using it in your procedure but instead work with local variable AItem: TPCEItem all the time? Well at least in your shown code that is.

  • Related