Delphi вызывает виртуальный конструктор на основе типа TObject

У меня есть объект, производный от объекта TStringList, который я называю "TAutoString". Это позволяет вам указать тип объекта при создании списка. Затем каждый раз, когда в список строк добавляется новая запись, она также создает копию объекта, связанного с этой строковой записью. Это упрощает хранение всех видов дополнительной информации вместе с каждой строкой. Например:

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

MO := TAutoString.Create(TMyObject);

Внутри объекта информация о классе хранится в переменной класса:

 private
  ObjectClass: TClass;


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

Теперь каждый раз, когда добавляется новый элемент, он создает новый объект указанного типа:

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

Теперь я могу добавлять или читать информацию, связанную с каждой строковой записью.

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

Это отлично работает, поскольку у объекта нет конструктора. Если у объекта есть конструктор, его конструктор не будет вызываться при создании объекта, потому что конструктор для TObject не является виртуальным.

Может ли кто-нибудь придумать способ обойти эту проблему. Я видел решения, которые используют библиотеки RTTI, но это в Delphi-7, в котором нет библиотеки RTTI.

Кстати, кажется немного странным, что конструктор TObject не виртуальный. Если бы это было так, это позволило бы использовать всевозможные полезные функции, подобные той, которую я пытаюсь реализовать.

РЕДАКТИРОВАТЬ: предложение Реми ниже было просто толчком, который мне был нужен. Изначально я пробовал аналогичную стратегию, но не смог заставить ее работать. Когда мне показалось, что это не работает так, как я думал, я предположил, что должно быть что-то, чего я не понимал в виртуальных методах. Его пост подтолкнул меня взглянуть на него еще раз. Оказалось, что я отказался от директивы Override для конструктора объекта, который хотел присоединить. Теперь все работает так, как должно.

Другая проблема, которая меня беспокоила, заключалась в том, что я уже использовал Auto Strings в группе других приложений, где объект был основан на "TObject", и я не хотел возвращаться и изменять весь этот код. Я решил эту проблему, перегрузив конструкторы и установив один для объектов на основе TObject, а другой - для объектов TAutoClass:

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

В зависимости от того, какой конструктор вызывается, класс объекта хранится по-разному в другой переменной.

 private
  AutoClass: TAutoClass;
  ObjectClass: TClass;

Затем, когда объект построен, я проверяю, какой из них был назначен, и использую его:

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

Новая версия отлично работает с любым типом объекта.

2 ответа

Решение

Чтобы сделать то, что вы хотите, вам нужно будет определить базовый класс для ваших объектов списка, от которого будут производиться, а затем вы можете добавить виртуальный конструктор к этому классу. Твой ObjectClass член должен будет использовать этот тип класса вместо использования TClass.

Например:

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;

...

Затем вы просто настраиваете свои производные классы объектов для использования TAutoStringObject вместо TObject, например:

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

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

И их конструктор будет вызван, как и ожидалось.

Вот подсказка для более чистого решения:

В Tobject можно добавить виртуальный конструктор.

Для этого вам нужно будет использовать так называемый «помощник класса».

Вот пример:

      type
    TobjectHelper = class helper for Tobject
    public
        constructor Create; virtual; // adds a virtual constructor to Tobject.
    end;

(Моим вариантом использования была функция TryCreateObject для обнаружения ситуации с нехваткой памяти во время создания объекта, обертывает try except end и просто возвращает true / false, чтобы предотвратить блоки try except в коде и вместо этого использовать более логические управляемые операторы if)

Помощники классов были представлены (?) В Delphi 8 и более поздних версиях. Ваши требования относятся к Delphi 7, поэтому это может не сработать.

Если вы не пишете код для Windows 95 / Windows 98 / Windows XP, возможно, вам пора перейти на более новую версию Delphi, особенно версии Delphi XE для поддержки юникода, иначе вы кодируете устаревшую платформу, которая вот-вот станет устаревшей. и т.п.

Однако для Windows 95 / Windows 98 и Windows XP я считаю, что Delphi 2007 может быть полезен, я считаю, что он может компилировать код, который может работать на этих старых платформах Windows, хотя я могу ошибаться.

Более поздние версии Delphi требуют наличия определенных системных библиотек Windows, иначе сборка / скомпилированный исполняемый файл не будет работать, w95 / w98 / wxp не имеют этих dll.

Другие вопросы по тегам