Local Pool для объектов

Анализируя исходный собственных программ, коих за годы накопилось не мало, я заметил что большое количество объектов живут в пределах вызова функции (процедуры, метода). Но при этом для каждого объекта нужно написать свой блок try finally. Если объектов много, все эти вложенные друг в друга try finally сильно засоряют код.

Пример функции отправки файла по протоколу http:

function SendFile( const URL, FileName: string ): Boolean;
var
  HTTP: THTTP;
  PostParams, Files: TStringList;
  Response: TMemoryStream;
begin
  PostParams := TStringList.Create;
  try
    PostParams[ 'login' ] := LOGIN;
    PostParams[ 'pass' ] := PASS;
    
    Files := TStringList.Create;
    try
      Files.Add( FileName );
    
      Response := TMemoryStream.Create;
      try
        Response.SetSize( DEF_RESP_SIZE );
        
        HTTP := THTTP.Create;
        try
          Result := HTTP.Post( URL, PostParams, Files, Response );
        finally
          HTTP.Free;
        end;
      finally
        Response.Free;
      end;
    finally
      Files.Free;
    end;
  finally
    PostParams.Free;
  end;
end;

«Лишние» строки выделены. Половина тела функции получилась «лишней».

Вот бы локальные объекты сами удалялись при выходе из функции. Если использовать интерфейсы (потомков IInterface) вместо объектов, то так и будет. Сработает механизм подсчета ссылок. Но не будешь же каждый стандартный класс оборачивать в интерфейс …

Хм … А может и не нужно каждый класс оборачивать?
Создадим объект с интерфейсной ссылкой, запоминающий все локальные объекты.
При выходе из функции он уничтожится, утянув за собой все локальные объекты.

Черновой вариант использования был таким:

function SendFile( const URL, FileName: string ): Boolean;
var
  HTTP: THTTP;
  PostParams, Files: TStringList;
  Response: TMemoryStream;
  LocalPool: ILocal;
begin
  LocalPool := TLocalPool.Create;
  
  LocalPool.Add( PostParams, TStringList.Create );
  PostParams[ 'login' ] := LOGIN;
  PostParams[ 'pass' ] := PASS;
  
  LocalPool.Add( Files, TStringList.Create );
  Files.Add( FileName );
  
  LocalPool.Add( Response, TMemoryStream.Create );
  Response.SetSize( DEF_RESP_SIZE );

  LocalPool.Add( HTTP, THTTP.Create );
  Result := HTTP.Post( URL, PostParams, Files, Response );
end;

Пока выглядит не очень. Нужно объявлять лишнюю переменную LocalPool, создавать ее TLocalPool.Create, писать LocalPool.Add при создании каждого объекта … Но даже так видно, что код сильно сократился. Стало лучше.

После некоторых изысканий пришел к иcпользованию функции LocalPool и конструкции with do begin end.

function SendFile( const URL, FileName: string ): Boolean;
var
  HTTP: THTTP;
  PostParams, Files: TStringList;
  Response: TMemoryStream;
begin
with LocalPool do begin
  Local( PostParams, TStringList.Create );
  PostParams[ 'login' ] := LOGIN;
  PostParams[ 'pass' ] := PASS;
  
  Local( Files, TStringList.Create );
  Files.Add( FileName );
  
  Local( Response, TMemoryStream.Create );
  Response.SetSize( DEF_RESP_SIZE );

  Local( HTTP, THTTP.Create );
  Result := HTTP.Post( URL, PostParams, Files, Response );
end;
end;

Локальные объекты создаются в методе Local.
Т. е. вместо

MyObject := TMyObject.Create;
try
  ...
finally
  MyObject.Free;
end;

пишем

Local( MyObject, TMyObject.Create );

Лаконично, функционально, наглядно. Никаких засоряющих код try finally и лишней вложенности кода. Только инструкции в чистом виде.

Как это работает. Функция LocalPool создает и возвращает интерфейсную ссылку ILocal. За этой ссылкой скрывается объект с TObjectList, который хранит все объекты, добавленные методом Local. При выходе из области видимости (в конце функции) интерфейсная ссылка обнуляется, объект ILocal уничтожается, а с ним и TObjectList со всеми объектами в нем.

Сам исходный код модуля получился весьма краток и прост.

Исходный код uvLocalPool (скачиваний: 9368)

Остался вопрос. Что будет при возникновении внутри блока with LocalPool do begin end исключения? Освободятся ли созданные объекты? Или здравствуй, утечка памяти? При использовании try finally объект то будет освобожден в любом случае.

Проверка показала, что все в порядке. Компилятор сам неявно добавляет try finally для освобождения интерфейсной ссылки.

Напоследок сравнение кода одной и той же функции до и после использования LocalPool, так сказать с высоты:

Оставьте комментарий