Pull to refresh

Дельфи. Автоматическое уничтожение объектов по выходу из метода

Reading time4 min
Views2K
День добрый.

Думаю, все дельфийцы тысячи раз писали подобный код:

var
  MyObj: TMyObj;
begin
  MyObj := TMyObj.Create;
  try
    MyObj.DoWork;   // работаем с MyObj
  finally
    MyObj.Free;
  end;
end;


Скажем так: такая писанина надоедает. Хочется, чтоб объекты сами уничтожались при выходе из функции/процедуры.
Что ж, это достаточно легко реализовать. В результате мы получим примерно такой код:
var
  MyObj: TMyObj;
begin
  MyOb := CreateObjectDestroyer(TMyOb.Create).ObjectAsPtr;
  MyObj.DoWork;   // работаем с MyObj
end; // тут объект MyObj уничтожится


Ниже я опишу как реализовать такое поведение.


Сразу укажу, что все код ниже будет работать во всех версиях дельфи, в которых есть интерфейсы (по моему начиная с третьей).

Как известно для интерфейсов Delphi ведет автоматический счет ссылок:

var
  Intf: IUnknown;
begin
  Intf := TInterfacedObject.Create; // тут неявно вызывается IUnknown.AddRef
  // что то делаем
end; // тут неявно вызывается IUnknown.Release и объект разрушается 


Теперь напишем наследника TInterfacedObject, который будет уничтожать в своем деструкторе переданный ему объект. Ну и плюс наш объект будет реализовывать маленький дополнительный интерфейс.

type
  IObjectDestroyer = interface(IInterface)
  ['{4DE81104-08B2-4821-960E-8935AC9B8F5E}']
    function GetObjectAsPtr: Pointer;
    property ObjectAsPtr: Pointer read GetObjectAsPtr; // это ссылка на тот объект, который мы хотим уничтожить
  end;

type
  TObjectDestroyer = class(TInterfacedObject, IObjectDestroyer)
  strict private
    FObject: TObject;
  protected
    function GetObjectAsPtr: Pointer;
  public
    constructor Create(AObject: TObject);
    destructor Destroy; override;
  end;

constructor TObjectDestroyer.Create(AObject: TObject);
begin
  inherited Create();
  FObject:=AObject;
end;

destructor TObjectDestroyer.Destroy;
begin
  FObject.Free;
  inherited Destroy;
end;

function TObjectDestroyer.GetObjectAsPtr: Pointer;
begin
  Result := FObject;
end;


Теперь можно работать с объектом так:

var
  Destroyer: IObjectDestroyer ;
begin
  Destroyer := TObjectDestroyer.Create(TMyObj.Create);
  TMyObj(Destroyer.ObjectAsPtr).DoWork();
end;


Мы избавились от необходимости явно уничтожать MyObj, но необходимость для доступа к объекту писать TMyObj(Destroyer.ObjectAsPtr) мягко говоря удручает. Ну что ж, опишем доп функцию:

function CreateObjectDestroyer(AObject: TObject): IObjectDestroyer;
begin
  Result:=TObjectDestroyer.Create(AObject);
end;  


и уберем из нашего кода в общем то ненужную нам переменную Destroyer:

var
  MyObj: TMyObj;
begin
  MyOb := CreateObjectDestroyer(TMyOb.Create).ObjectAsPtr;
  MyObj.DoWork;
end; 


Вызов CreateObjectDestroyer неявно создаем переменную (то что в прошлом варианте мы описали как Destroyer).

Ну собственно задача решена, но хотелось бы упоминать о некоторых неочевидных моментах и откровенных недостатках такого подхода:

Итак, первое — типизация. Точнее её отсутствие. свойство ObjectAsPtr специально объявлено как Pointer чтобы его можно было присвоить переменной любого класса:

var
  Obj1: TMyObj1;
  Obj2: TMyObj2;
begin
  Obj1 :=  CreateObjectDestroyer(TMyObj1.Create).ObjectAsPtr;
  Obj2 :=  CreateObjectDestroyer(TMyObj2.Create).ObjectAsPtr;
end;


но в таком случае компилятор не следит за типами и мы можем накосячить:
Obj1 :=  CreateObjectDestroyer(TMyObj2.Create).ObjectAsPtr;

скорее всего в таком случае на ждем Access violation

Можно добавить в интерфейс IObjectDestroyer свойство AObject: TObject и в явном виде приводить типы
Obj1 :=  CreateObjectDestroyer(TMyObj2.Create).AObject as TMyObj1;


тут ошибка будет более явной, но все равно это проверка в ран-тайм. В компил тайм для дельфей младше 2009 версии эта проблема не решается, в старших версиях появились дженерики, там можно типизацией не жертвовать.

Вторая проблема: неявный момент уничтожения объекта.

В теории компилятор может вызвать Release для непосредственно после вызова CreateObjectDestroyer
Obj := CreateObjectDestroyer(TMyObj.Create).ObjectAsPtr;
// Ссылка Obj невалидна так как интерфейс зарелизин и объект MyObj уничтожен.

Однако Barry Kelly говорит, что такое поведении не будет меняться в будущем, можно почитать обсуждение здесь

Опять же как и вопрос с типизацией в 2009/2010 дельфе можно частично решить и данный недостаток.

Ну в общем то все, если Вам понравится этот пост, я опишу как более прозрачно реализовать аналогичный механизм на старших версиях дельфи.

ЗЫ Посоветуйте аналог source code highlighter, который бы поддерживал дельфийский синтаксис?
Tags:
Hubs:
Total votes 18: ↑11 and ↓7+4
Comments27

Articles