0

ネストされたtry/finalブロックをルーチンから再利用可能なエンティティにどのように「抽出」しますか?私が持っていると言う

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) thenTDoSomething.Done

4

3 に答える 3

5

このイディオムを安全かつ簡単にするクラスには、次の3つのことがあります。

  1. コンストラクターのメモリー割り当てフェーズ中(実際のコンストラクター本体が実行される前)、クラス参照フィールドはnilに初期化されます。
  2. コンストラクタで例外が発生すると、デストラクタが自動的に呼び出されます。
  3. null参照を呼び出すことは常に安全なFreeので、最初にチェックする必要はありませんAssigned

デストラクタはすべてのフィールドに依存して既知の値を持つことができるためFree、コンストラクタがクラッシュするまでの距離に関係なく、すべてを安全に呼び出すことができます。各フィールドは、有効なオブジェクト参照を保持するか、nilになります。どちらの場合も、安全に解放できます。

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;
于 2011-04-07T14:30:07.997 に答える
1

Delphiソースでは、Assignedinfinallyでテストされたパターンが使用されます。同じことをしますが、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すると、AcquireResource2で例外が発生した場合にResource1が解放されません。

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 に答える
1

はい、初期化がゼロの複数のリソースに対して、単一のtry / final/endブロックを使用できます。

別の可能な解決策は、BarryKellyブログにあります。

于 2011-04-07T13:58:03.267 に答える