Извлечь вложенные блоки try / finally - PullRequest
0 голосов
/ 07 апреля 2011

Как бы вы "извлекли" вложенные блоки try / finally из подпрограммы в повторно используемую сущность? Скажи, что у меня есть

procedure DoSomething;
var
  Resource1: TSomeKindOfHandleOrReference1;
  Resource2: TSomeKindOfHandleOrReference2;
  Resource3: TSomeKindOfHandleOrReference3;
begin
  AcquireResource1;
  try
    AcquireResource2;
    try
      AcquireResource3;
      try
        // Use the resources
      finally
        ReleaseResource3;
      end;
    finally
      ReleaseResource2;
    end;
  finally
    ReleaseResource1;
  end;
end;

и хотите что-то вроде

TDoSomething = record // or class
strict private
  Resource1: TSomeKindOfHandleOrReference1;
  Resource2: TSomeKindOfHandleOrReference2;
  Resource3: TSomeKindOfHandleOrReference3;
public
  procedure Init; // or constructor
  procedure Done; // or destructor
  procedure UseResources;
end;

procedure DoSomething;
var
  Context: TDoSomething;
begin
  Context.Init;
  try
    Context.UseResources;
  finally
    Context.Done;
  end;
end;

Я хочу, чтобы это имело ту же исключительную безопасность, что и вложенный оригинал. Достаточно ли выполнить нулевую инициализацию переменных ResourceN в TDoSomething.Init и выполнить некоторые проверки if Assigned(ResourceN) then в TDoSomething.Done?

Ответы [ 3 ]

5 голосов
/ 07 апреля 2011

В классах есть три вещи, которые делают эту идиому безопасной и простой:

  1. На этапе выделения памяти конструктору (до запуска реального тела конструктора) поля ссылки на класс инициализируютсяна ноль.
  2. Когда в конструкторе возникает исключение, деструктор вызывается автоматически.
  3. Всегда безопасно вызывать Free для нулевой ссылки, поэтому вам не нужно проверять Assigned first.

Поскольку деструктор может полагаться на то, что все поля имеют известные значения, он может безопасно вызывать Free во всем, независимо от того, как далеко зашел конструктор до сбоя.Каждое поле будет содержать действительную ссылку на объект или оно будет нулевым, и в любом случае его можно безопасно освободить.

constructor TDoSomething.Create;
begin
  Resource1 := AcquireResource1;
  Resource2 := AcquireResource2;
  Resource3 := AcquireResource3;
end;

destructor TDoSomething.Destroy;
begin
  Resource1.Free;
  Resource2.Free;
  Resource3.Free;
end;

Используйте его так же, как и любой другой класс:

Context := TDoSomething.Create;
try
  Context.UseResources;
finally
  Context.Free;
end;
1 голос
/ 07 апреля 2011

Шаблон с тестированием на Assigned in finally используется в источнике Delphi. Вы делаете то же самое, но я думаю, что вы должны переместить Context.Init, чтобы захватить исключение из Context.Init.

procedure DoSomething;
var
  Context: TDoSomething;
begin
  try
    Context.Init;
    Context.UseResources;
  finally
    Context.Done;
  end;
end;

Редактировать 1 Вот как вы должны это сделать без Context.Init и Context.Done. Если вы поместите весь код AquireResource до try, вы не освободите Resource1, если получите исключение в AcquireResource2

procedure DoSomething;
var
    Resource1: TSomeKindOfHandleOrReference1;
    Resource2: TSomeKindOfHandleOrReference2;
    Resource3: TSomeKindOfHandleOrReference3;
begin
    Resource1 := nil;
    Resource2 := nil;
    Resource3 := nil;
    try
        AcquireResource1;
        AcquireResource2;
        AcquireResource3;

        //Use the resources

    finally
        if assigned(Resource1) then ReleaseResource1;
        if assigned(Resource2) then ReleaseResource2;
        if assigned(Resource3) then ReleaseResource3;
    end;
end;
1 голос
/ 07 апреля 2011

Да, вы можете использовать один блок try / finally / end для нескольких ресурсов с нулевой инициализацией.

Другое возможное решение можно найти в блоге Барри Келли

...