delphiconstructorpolymorphismtobject

Delphi calling a virtual constructor based on the TObject type


I have an object that is derived from the TStringList object that I call a "TAutoString." It allows you to specify an object type when the list is created. Then each time a new entry is added to the string list, it also creates a copy of the object associated with that string entry. This makes it easy to store all kinds of additional information along with each string. For example:

type TMyObject = class(TObject)
 public
  Cats: integer;
  Dogs: integer;
  Mice: integer;
 end;

MO := TAutoString.Create(TMyObject);

Inside the object, the class information is stored in a class variable:

 private
  ObjectClass: TClass;


constructor TAutoString.Create(ObjClass: TClass);
begin
    inherited Create;
    ObjectClass:=ObjClass;
end;

Now, every time a new item is added, it creates a new object of the specified type:

function TAutoString.Add(const S: string): Integer;
begin
    Result:=inherited Add(S);
    Objects[Result]:=ObjectClass.Create;
end;

I can now add or read information associated with each string entry.

   TMyObject(MO.Objects[25]).Cats := 17;
   D:=TMyObject(MO.Objects[25]).Dogs;

This works great as along as the object doesn't have a constructor. If the object has a constructor, its constructor won't get called when the object is created because the constructor for TObject is not virtual.

Can anyone think of a way around this problem. I've seen solutions that use the RTTI libraries, but this is in Delphi-7, which doesn't have an RTTI library.

As an aside, it seems a bit strange that TObject's constructor is not virtual. If it were, it would enable all sorts of useful features like the one I'm trying to implement.

EDIT: Remy's suggestion below was just the nudge I needed. I had originally tried a similar strategy, but I couldn't make it work. When it didn't seem to work the way I thought it should, I assumed there must be something that I didn't understand about virtual methods. His post pushed me to look at it again. It turned out that I had left off the "Override" directive for the constructor of the object I wanted to attach. Now it works just the way it should.

The other issue I was concerned about was that I had already used the Auto Strings in a bunch of other applications where the object was based on "TObject" and I didn't want to go back and change all that code. I solved that issue by overloading the constructors and having one for TObject-based objects and another my TAutoClass objects:

  constructor Create(ObjClass: TAutoClass); overload; virtual;
  constructor Create(ObjClass: TClass); overload; virtual;

Depending on which constructor is called, the object class stored in a different in a different variable.

 private
  AutoClass: TAutoClass;
  ObjectClass: TClass;

Then when the object is constructed I check to see which has been assigned and use that one:

procedure TAutoString.CreateClassInstance(Index: integer);
begin
   if AutoClass<>nil then Objects[Index]:=AutoClass.Create
   else Objects[Index]:=ObjectClass.Create
end;

The new version works perfectly with either type of object.


Solution

  • To do what you want, you will have to define a base class for your list objects to derive from, and then you can add a virtual constructor to that class. Your ObjectClass member will have to use that class type instead of using TClass.

    For example:

    type
      TAutoStringObject = class(TObject)
      public
        constructor Create; virtual;
      end;
    
      TAutoStringObjectClass = class of TAutoStringObject;
    
      TAutoString = class(TStringList)
      private
        ObjectClass: TAutoStringObjectClass;
      public
        constructor Create(ObjClass: TAutoStringObjectClass);
        function Add(const S: string): Integer; override;
        ...
      end;
    
    ...
    
    constructor TAutoStringObject.Create;
    begin
      inherited Create;
    end;
    
    constructor TAutoString.Create(ObjClass: TAutoStringObjectClass);
    begin
      inherited Create;
      ObjectClass := ObjClass;
    end;
    
    function TAutoString.Add(const S: string): Integer;
    var
      Obj: TAutoStringObject;
    begin
      Obj := ObjectClass.Create;
      try
        Result := inherited AddObject(S, Obj);
      except
        Obj.Free;
        raise;
      end;
    end;
    
    ...
    

    Then, you simply adjust your derived object classes to use TAutoStringObject instead of TObject, eg:

    type
      TMyObject = class(TAutoStringObject)
      public
        ...
        constructor Create; override;
      end;
    
    MO := TAutoString.Create(TMyObject);
    ...
    

    And their constructor will be called, as expected.