Delphi chiama un costruttore virtuale basato sul tipo TObject

Sep 12 2020

Ho un oggetto derivato dall'oggetto TStringList che chiamo "TAutoString". Consente di specificare un tipo di oggetto quando viene creato l'elenco. Quindi ogni volta che viene aggiunta una nuova voce all'elenco delle stringhe, viene creata anche una copia dell'oggetto associato a quella voce della stringa. Ciò semplifica la memorizzazione di tutti i tipi di informazioni aggiuntive insieme a ciascuna stringa. Per esempio:

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

MO := TAutoString.Create(TMyObject);

All'interno dell'oggetto, le informazioni sulla classe sono memorizzate in una variabile di classe:

 private
  ObjectClass: TClass;


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

Ora, ogni volta che viene aggiunto un nuovo elemento, crea un nuovo oggetto del tipo specificato:

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

Ora posso aggiungere o leggere le informazioni associate a ciascuna voce di stringa.

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

Funziona alla grande finché l'oggetto non ha un costruttore. Se l'oggetto ha un costruttore, il suo costruttore non verrà chiamato quando l'oggetto viene creato perché il costruttore per TObject non è virtuale.

Qualcuno può pensare a un modo per aggirare questo problema. Ho visto soluzioni che usano le librerie RTTI, ma questo è in Delphi-7, che non ha una libreria RTTI.

Per inciso, sembra un po 'strano che il costruttore di TObject non sia virtuale. Se lo fosse, abiliterebbe tutti i tipi di funzionalità utili come quella che sto cercando di implementare.

EDIT: Il suggerimento di Remy di seguito era solo la spinta di cui avevo bisogno. Inizialmente avevo provato una strategia simile, ma non riuscivo a farla funzionare. Quando non sembrava funzionare come pensavo, pensavo che ci fosse qualcosa che non capivo sui metodi virtuali. Il suo post mi ha spinto a guardarlo di nuovo. Si è scoperto che avevo omesso la direttiva "Override" per il costruttore dell'oggetto che volevo allegare. Ora funziona proprio come dovrebbe.

L'altro problema di cui ero preoccupato era che avevo già utilizzato le stringhe automatiche in un mucchio di altre applicazioni in cui l'oggetto era basato su "TObject" e non volevo tornare indietro e cambiare tutto quel codice. Ho risolto il problema sovraccaricando i costruttori e avendone uno per gli oggetti basati su TObject e un altro i miei oggetti TAutoClass:

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

A seconda del costruttore chiamato, la classe dell'oggetto memorizzata in un diverso in una variabile diversa.

 private
  AutoClass: TAutoClass;
  ObjectClass: TClass;

Quindi, quando l'oggetto è costruito, controllo per vedere quale è stato assegnato e lo uso:

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

La nuova versione funziona perfettamente con entrambi i tipi di oggetto.

Risposte

3 RemyLebeau Sep 12 2020 at 03:54

Per fare quello che vuoi, dovrai definire una classe base da cui derivare gli oggetti dell'elenco, quindi puoi aggiungere un costruttore virtuale a quella classe. Il tuo ObjectClassmembro dovrà usare quel tipo di classe invece di usare TClass.

Per esempio:

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;

...

Quindi, devi semplicemente regolare le classi di oggetti derivati ​​da utilizzare TAutoStringObjectinvece di TObject, ad esempio:

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

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

E il loro costruttore verrà chiamato, come previsto.