Delphi appelant un constructeur virtuel basé sur le type TObject

Sep 12 2020

J'ai un objet dérivé de l'objet TStringList que j'appelle un "TAutoString". Il vous permet de spécifier un type d'objet lors de la création de la liste. Ensuite, chaque fois qu'une nouvelle entrée est ajoutée à la liste de chaînes, elle crée également une copie de l'objet associé à cette entrée de chaîne. Cela facilite le stockage de toutes sortes d'informations supplémentaires avec chaque chaîne. Par exemple:

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

MO := TAutoString.Create(TMyObject);

À l'intérieur de l'objet, les informations de classe sont stockées dans une variable de classe:

 private
  ObjectClass: TClass;


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

Désormais, chaque fois qu'un nouvel élément est ajouté, il crée un nouvel objet du type spécifié:

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

Je peux maintenant ajouter ou lire des informations associées à chaque entrée de chaîne.

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

Cela fonctionne très bien tant que l'objet n'a pas de constructeur. Si l'objet a un constructeur, son constructeur ne sera pas appelé lorsque l'objet est créé car le constructeur de TObject n'est pas virtuel.

Quelqu'un peut-il penser à un moyen de contourner ce problème. J'ai vu des solutions qui utilisent les bibliothèques RTTI, mais c'est dans Delphi-7, qui n'a pas de bibliothèque RTTI.

En passant, il semble un peu étrange que le constructeur de TObject ne soit pas virtuel. Si c'était le cas, cela activerait toutes sortes de fonctionnalités utiles comme celle que j'essaie d'implémenter.

EDIT: La suggestion de Remy ci-dessous était juste le coup de pouce dont j'avais besoin. J'avais initialement essayé une stratégie similaire, mais je ne pouvais pas la faire fonctionner. Quand cela ne semblait pas fonctionner comme je le pensais, j'ai supposé qu'il devait y avoir quelque chose que je ne comprenais pas à propos des méthodes virtuelles. Son message m'a poussé à le revoir. Il s'est avéré que j'avais laissé la directive "Override" pour le constructeur de l'objet que je voulais attacher. Maintenant, cela fonctionne comme il se doit.

L'autre problème qui m'inquiétait était que j'avais déjà utilisé les chaînes automatiques dans un tas d'autres applications où l'objet était basé sur "TObject" et je ne voulais pas revenir en arrière et changer tout ce code. J'ai résolu ce problème en surchargeant les constructeurs et en en ayant un pour les objets basés sur TObject et un autre pour mes objets TAutoClass:

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

Selon le constructeur appelé, la classe d'objets stockée dans un différent dans une variable différente.

 private
  AutoClass: TAutoClass;
  ObjectClass: TClass;

Ensuite, lorsque l'objet est construit, je vérifie ce qui a été attribué et je l'utilise:

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

La nouvelle version fonctionne parfaitement avec l'un ou l'autre type d'objet.

Réponses

3 RemyLebeau Sep 12 2020 at 03:54

Pour faire ce que vous voulez, vous devrez définir une classe de base pour les objets de votre liste à dériver, puis vous pourrez ajouter un constructeur virtuel à cette classe. Votre ObjectClassmembre devra utiliser ce type de classe au lieu d'utiliser TClass.

Par exemple:

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;

...

Ensuite, vous ajustez simplement vos classes d'objets dérivés à utiliser à la TAutoStringObjectplace de TObject, par exemple:

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

MO := TAutoString.Create(TMyObject);
...

Et leur constructeur sera appelé, comme prévu.