Delphi llama a un constructor virtual basado en el tipo TObject

Sep 12 2020

Tengo un objeto que se deriva del objeto TStringList que llamo "TAutoString". Le permite especificar un tipo de objeto cuando se crea la lista. Luego, cada vez que se agrega una nueva entrada a la lista de cadenas, también crea una copia del objeto asociado con esa entrada de cadena. Esto facilita el almacenamiento de todo tipo de información adicional junto con cada cadena. Por ejemplo:

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

MO := TAutoString.Create(TMyObject);

Dentro del objeto, la información de la clase se almacena en una variable de clase:

 private
  ObjectClass: TClass;


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

Ahora, cada vez que se agrega un nuevo elemento, crea un nuevo objeto del tipo especificado:

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

Ahora puedo agregar o leer información asociada con cada entrada de cadena.

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

Esto funciona muy bien siempre que el objeto no tenga un constructor. Si el objeto tiene un constructor, no se llamará a su constructor cuando se cree el objeto porque el constructor de TObject no es virtual.

¿Alguien puede pensar en una forma de solucionar este problema? He visto soluciones que usan las bibliotecas RTTI, pero esto está en Delphi-7, que no tiene una biblioteca RTTI.

Aparte, parece un poco extraño que el constructor de TObject no sea virtual. Si lo fuera, habilitaría todo tipo de funciones útiles como la que estoy tratando de implementar.

EDITAR: La sugerencia de Remy a continuación fue solo el empujón que necesitaba. Originalmente había intentado una estrategia similar, pero no pude hacer que funcionara. Cuando no pareció funcionar de la manera que pensé que debería, asumí que debía haber algo que no entendía sobre los métodos virtuales. Su publicación me empujó a mirarla de nuevo. Resultó que había dejado la directiva "Override" para el constructor del objeto que quería adjuntar. Ahora funciona como debería.

El otro problema que me preocupaba era que ya había usado Auto Strings en un montón de otras aplicaciones donde el objeto estaba basado en "TObject" y no quería volver atrás y cambiar todo ese código. Resolví ese problema sobrecargando los constructores y teniendo uno para objetos basados ​​en TObject y otro para mis objetos TAutoClass:

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

Dependiendo de qué constructor se llame, la clase de objeto se almacena en una variable diferente.

 private
  AutoClass: TAutoClass;
  ObjectClass: TClass;

Luego, cuando se construye el objeto, verifico cuál se ha asignado y uso ese:

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

La nueva versión funciona perfectamente con cualquier tipo de objeto.

Respuestas

3 RemyLebeau Sep 12 2020 at 03:54

Para hacer lo que desee, tendrá que definir una clase base de la que derivarán sus objetos de lista, y luego podrá agregar un constructor virtual a esa clase. Tu ObjectClassmiembro tendrá que usar ese tipo de clase en lugar de usar TClass.

Por ejemplo:

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;

...

Luego, simplemente ajuste sus clases de objetos derivados para usar en TAutoStringObjectlugar de TObject, por ejemplo:

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

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

Y se llamará a su constructor, como se esperaba.