1
votes

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.

1
Some classes have constructors with parameters. How would you handle those? And since you are using Delphi 7, generics are out of the question, too. - Andreas Rejbrand

1 Answers

3
votes

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.