0

您如何将嵌套的 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;

我希望它具有与嵌套原始文件相同的异常安全性。TDoSomething.Init将 ResourceN 变量初始化为零并进行一些if Assigned(ResourceN) then检查是否足够TDoSomething.Done

4

3 回答 3

5

There are three things about classes that make this idiom safe and easy:

  1. During the memory-allocation phase of the constructor (before the real constructor body runs), class-reference fields get initialized to nil.
  2. When an exception occurs in a constructor, the destructor is called automatically.
  3. It's always safe to call Free on a null reference, so you never need to check Assigned first.

Since the destructor can rely on all fields to have known values, it can safely call Free on everything, regardless of how far the constructor got before crashing. Each field will either hold a valid object reference or it will be nil, and either way, it's safe to free it.

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

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

Use it the same way you use any other class:

Context := TDoSomething.Create;
try
  Context.UseResources;
finally
  Context.Free;
end;
于 2011-04-07T14:30:07.997 回答
1

是的,您可以将单个 try/finally/end 块用于具有零初始化的多个资源。

另一种可能的解决方案可以在Barry Kelly 博客中找到

于 2011-04-07T13:58:03.267 回答
1

Delphi 源代码中使用了最后对 Assigned 进行测试的模式。你做了同样的事情,但我认为你应该移动 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;
于 2011-04-07T14:00:52.470 回答