2

I am having trouble with the incorrect virtual Create() method being called during dynamic object creation. The parent method is called rather than the decendant method.

I have reviewed these posts, but can't figure it out:
Delphi - Create class from a string

Exploring TRTTIType and Descendants

Can I pass a Class type as a procedure parameter

and here Class_References

I have the following classes:

TCellObj = class(TPhysicsObj)
  ...
  public
    constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager
    destructor Destroy;
    ...
  end;

  TCellObjClass = Class of TCellObj;

--------------------------------

 TCellTrialAObj = class(TCellObj)
    ...
    public
      ...
      constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager
  end;
--------------------------------

TRgnManager = class (TObject)
  ... 
  public
    function NewCell(ClassRef : TCellObjClass) : TCellObj; 
    ...
  end;

  ....

  function TRgnManager.NewCell(ClassRef : TCellObjClass) : TCellObj;
  var CellObj : TCellObj;

  begin
    CellObj := ClassRef.Create(Self);
    CellObj.DefaultInitialize;
    CellObj.Color := TAlphaColors.Slategray;
    FCellsList.Add(CellObj);  //This will own objects.
    SetSelection(CellObj);
    Result := CellObj;
  end;

And finally I start the process of dynamic object creation by the following line:

RgnManager.NewCell(TCellTrialAObj);

My goal is to have TRgnManager.NewCell to create any decendant of TCellObj based on the derived class passed in as a parameter. I will typecast the result to the appropriate class type during use.

When I step through the code with a debugger in NewCell, the Evaluate/Modify tool tells me that ClassRef = TCellTrialAObj as expected.

But when I step into the ClassRef.Create(self) line, it goes to TCellObj.Create(), NOT to TCellTrialAObj.Create() as I would have expected. This is the part I don't understand.

After the result has been assigned to CellObj, the Evaluate/Modify tool tells me that CellObj.ClassName = 'TCellTrialAObj';

So of ClassRef was of TCellTrialAObj, then why didn't the Create() function call TCellTrialAObj.Create() ??

Thanks in advance.

P.S. I am using Embarcadero® Delphi 10 Seattle Version 23.0.22248.5795

ADDENDUM

I cobbled together this function below, using examples from links above. It seems to work, and calls TCellTrialAObj.Create as desired. But I don't understand exactly how, why, or if I am actually doing it right. Can anyone explain?

  function TRgnManager.NewCell(ClassRef : TCellObjClass) : TCellObj;
  var CellObj : TCellObj;
      RT : TRttiType;
      C : TRttiContext;
      T : TRttiInstanceType;
      V : TValue;

  begin
    C := TRttiContext.Create;
    T := (C.GetType(ClassRef) as TRttiInstanceType);
    V := T.GetMethod('Create').Invoke(T.metaClassType,[self]);
    C.Free;
    CellObj := V.AsObject as TCellObj;

    //CellObj := ClassRef.Create(Self);
    CellObj.DefaultInitialize;
    CellObj.Color := TAlphaColors.Slategray;
    FCellsList.Add(CellObj);  //This will own objects.
    SetSelection(CellObj);
    Result := CellObj;
  end;
kdtop
  • 541
  • 1
  • 7
  • 22
  • 1
    Are you drowning in warnings and hints or why did you miss the compiler telling you whats wrong? – Stefan Glienke May 24 '17 at 22:25
  • 1
    Destructor should have override on it too. And you can use forward declaration on TRgnManager so that it can be used as param. Many more problems in the code than the single one you identified. – David Heffernan May 25 '17 at 06:09
  • @StefanGlienke and DavidHeffernan Thanks for the feedback. See other comments as I try to educate myself and avoid future similar problems. – kdtop May 25 '17 at 14:44

1 Answers1

4

Compiler warnings will help you out here. You'll probably notice when compiling that you get the warning Method 'Create' hides virtual method of base type 'TCellObj'. This is because you have declared the constructor of the descendent TCellTrialAObj as virtual when we instead infer that you want it to be override.

Here a minimal example demonstrates the functionality you want.

program Project1;

{$APPTYPE CONSOLE}

type
  TCellObj = class
    public
      constructor Create; virtual;
  end;

  TCellObjClass = Class of TCellObj;

  TCellTrialAObj = class(TCellObj)
    public
      constructor Create; override;
  end;

constructor TCellObj.Create;
begin
  WriteLn('TCellObj');
end;

constructor TCellTrialAObj.Create;
begin
  WriteLn('Calling base constructor...');
  inherited;
  WriteLn('...and now in TCellTrialAObj constructor');
end;

function NewCell(ClassRef : TCellObjClass) : TCellObj;
var
  CellObj : TCellObj;
begin
  CellObj := ClassRef.Create;;
  Result := CellObj;
end;

var
  LCellObj : TCellObj;
begin
  LCellObj := NewCell(TCellTrialAObj);
  ReadLn;
end.

As an aside, here you are using a comment to suggest a type restriction :

 constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager

It is possible, however, to make a forward declaration of the TRgnMgr class and fully define it later, allowing you to include the much more robust formal type restriction.

  TRgnMgr = class;  { Declare type... }

  TCellObj = class
    public
      constructor Create(RgnMgr : TRgnMgr); virtual;
  end;

  TCellObjClass = Class of TCellObj;

  TCellTrialAObj = class(TCellObj)
    public
      constructor Create(RgnMgr : TRgnMgr); override;
  end;

  TRgnMgr = class  { but define it later }
    private
      FFoo : integer;
  end;
J...
  • 30,968
  • 6
  • 66
  • 143
  • Thanks @J... that did it. So easy when you understand... :-) Much more straight-forward than that crazy RTTI solution I put as an addendum to the original question. Thanks again. – kdtop May 24 '17 at 17:56
  • 1
    @kdtop Do see my edit about type restrictions - I think this would also be of interest. – J... May 24 '17 at 18:00
  • 2
    @kdtop Over and above understanding polymorphism, I hope you learn the lesson to: "Never ignore compiler warnings." They're telling you something is at least suspicious, if not outright _wrong_. You can _always_ modify your code so that it doesn't emit a warning. So you should make a point of understanding and eliminating any compiler warnings you see. – Disillusioned May 25 '17 at 03:08
  • @J... I knew about forward class declarations, but I thought that the forward had to be defined *in the same file*. In my case I have an entire separate unit for TRgnMgr. It's one of those circular reference problems. I'll have to try and see if it will work across separate files. – kdtop May 25 '17 at 14:37
  • @CraigYoung I'll admit my knowledge is weak here. I have a multi-unit hierarchy of descendant units. The first unit to introduce a constructor that takes RgnMgr as a parameter gives me an error of unmatching signatures if I try to use the override directive, and it gives me that warning about hiding the ancestor if I use the virtual directive. So when I have encountered this in the past, I didn't know how to fix it, and thus have gotten into the habit of ignoring it. How should I handle that situation? – kdtop May 25 '17 at 14:42
  • 1
    @kdtop You're right, but I would probably argue that, if the classes are directly mutually dependent, then they probably *belong* in the same unit. – J... May 25 '17 at 14:42
  • 1
    @kdtop Per your question to Craig, I think that could be the subject of a new, separate question. See : https://meta.stackoverflow.com/q/266767/327083 – J... May 25 '17 at 14:45
  • 1
    @kdtop As J says, ordinarily a new question should be asked. But in this case, if you search, you'll find this and related questions have been asked here before. Try the following in google: `site:stackoverflow.com delphi "hides virtual method of base"` I'll just forewarn that in your search you'll come across the **reintroduce** keyword. This is a so-called "feature" of Delphi that suppresses the warning about breaking polymorphism in a hierarchy. NB: It doesn't _fix_ anything, and you'd be better off reconsidering your OO design. You should not need to hide any virtual methods. – Disillusioned May 26 '17 at 00:39
  • Thanks so much everyone for your very helpful replies! I'll keep searching to figure this out. – kdtop May 26 '17 at 12:49