19

私は今朝、ネストされた試行を回避するためにこの考えを持っていました。最終的には次のようにブロックされます

procedure DoSomething;
var
  T1, T2, T3 : TTestObject;
begin
  T1 := TTestObject.Create('One');
  try
    T2 := TTestObject.Create('Two');
    try
      T3 := TTestObject.Create('Three');
      try
        //A bunch of code;
      finally
        T3.Free;
      end;
    finally
      T2.Free;
    end;
  finally
    T1.Free;
  end;
end;

インターフェイスの自動参照カウントを利用することで、

Type  
  IDoFinally = interface
    procedure DoFree(O : TObject);
  end;

  TDoFinally = class(TInterfacedObject, IDoFinally)
  private
    FreeObjectList : TObjectList;
  public
    procedure DoFree(O : TObject);
    constructor Create;
    destructor Destroy; override;
  end;

//...

procedure TDoFinally.DoFree(O : TObject);
begin
  FreeObjectList.Add(O);
end;

constructor TDoFinally.Create;
begin
  FreeObjectList := TObjectList.Create(True);
end;

destructor TDoFinally.Destroy;
begin
  FreeObjectList.Free;
  inherited;
end;

そのため、前のコード ブロックは次のようになります。

procedure DoSomething;
var
  T1, T2, T3 : TTestObject;
  DoFinally : IDoFinally;
begin
  DoFinally := TDoFinally.Create;
  T1 := TTestObject.Create('One');
  DoFinally.DoFree(T1);
  T2 := TTestObject.Create('Two');
  DoFinally.DoFree(T2);
  T3 := TTestObject.Create('Three');
  DoFinally.DoFree(T3);
  // A Bunch of code;
end;

私の質問は次のとおりです。これは機能しますか、それとも何かを見落としていますか?

私にはこれはかなりクールに見え、ネストの量が減ったことでコードが少し読みやすくなりました。ファイルを閉じる、クエリなどを実行するために実行する匿名メソッドのリストを保存するように拡張することもできます...

4

9 に答える 9

21

はい、動作します。

おそらく、元のコードのネストされた try-finally ブロックと、参照カウント オブジェクトを使用して他のオブジェクトの有効期間を管理する手法との間の唯一の違いは、オブジェクトのいずれかを破棄する際に問題が発生した場合に何が起こるかということです。オブジェクトの破棄中に例外が発生した場合、ネストされた try-finally ブロックにより、残りのオブジェクトが引き続き解放されることが保証されます。TObjectListあなたの中でTDoFinallyそれはしません。リスト内のアイテムを破棄できない場合、リスト内の後続のアイテムはリークされます。

ただし、実際には、それは実際には問題ではありません。デストラクタが例外をスローすることはありません。もしそうなら、とにかくそれから回復する方法は本当にないので、それが原因で何かが漏れても問題ありません. いずれにせよ、プログラムは一時的に終了する必要があるため、きちんとしたクリーンアップ ルーチンを用意することはほとんど重要ではありません。

ちなみに、JCL は、ローカル オブジェクトの有効期間を管理するためのISafeGuardおよびインターフェイスを既に提供しています。IMultiSafeGuardたとえば、次のようにコードを書き直すことができます。

uses JclSysUtils;

procedure DoSomething;
var
  T1, T2, T3: TTestObject;
  G: IMultiSafeGuard;
begin
  T1 := TTestObject(Guard(TTestObject.Create('One'), G));
  T2 := TTestObject(Guard(TTestObject.Create('Two'), G));
  T3 := TTestObject(Guard(TTestObject.Create('Three'), G));
  // A Bunch of code;
end;

そのライブラリは、デストラクタの例外にも対処しません。

于 2013-08-28T20:16:11.887 に答える
8

@JRL のアプローチをより理解しやすくするための一連のヘルパー関数があります。

procedure InitialiseNil(var Obj1); overload;
procedure InitialiseNil(var Obj1, Obj2); overload;
procedure InitialiseNil(var Obj1, Obj2, Obj3); overload;

procedure FreeAndNil(var Obj1); overload;
procedure FreeAndNil(var Obj1, Obj2); overload;
procedure FreeAndNil(var Obj1, Obj2, Obj3); overload;

実際、私のコードにはさらに多くのパラメータを持つバージョンがあります。メンテナンスを容易にするために、このコードはすべて短い Python スクリプトから自動的に生成されます。

これらのメソッドは、明らかな方法で実装されています。

procedure FreeAndNil(var Obj1, Obj2);
var
  Temp1, Temp2: TObject;
begin
  Temp1 := TObject(Obj1);
  Temp2 := TObject(Obj2);
  Pointer(Obj1) := nil;
  Pointer(Obj2) := nil;
  Temp1.Free;
  Temp2.Free;
end;

これにより、質問のコードを次のように書き直すことができます。

InitialiseNil(T1, T2, T3);
try
  T1 := TTestObject.Create('One');
  T2 := TTestObject.Create('Two');
  T3 := TTestObject.Create('Three');
finally
  FreeAndNil(T3, T2, T1);
end;

そして Python スクリプト:

count = 8


def VarList(count, prefix):
    s = ""
    for i in range(count):
        if i != 0:
            s = s + ", "
        s = s + prefix + str(i + 1)
    return s


def InitialiseNilIntf(count):
    print("procedure InitialiseNil(var " + VarList(count, "Obj") + "); overload;")


def FreeAndNilIntf(count):
    print("procedure FreeAndNil(var " + VarList(count, "Obj") + "); overload;")


def InitialiseNilImpl(count):
    print("procedure InitialiseNil(var " + VarList(count, "Obj") + ");")
    print("begin")
    for i in range(count):
        print("  Pointer(Obj%s) := nil;" % str(i + 1))
    print("end;")
    print()


def FreeAndNilImpl(count):
    print("procedure FreeAndNil(var " + VarList(count, "Obj") + ");")
    print("var")
    print("  " + VarList(count, "Temp") + ": TObject;")
    print("begin")
    for i in range(count):
        print("  Temp%s := TObject(Obj%s);" % (str(i + 1), str(i + 1)))
    for i in range(count):
        print("  Pointer(Obj%s) := nil;" % str(i + 1))
    for i in range(count):
        print("  Temp%s.Free;" % str(i + 1))
    print("end;")
    print()


for i in range(count):
    InitialiseNilIntf(i + 1)
print()
for i in range(count):
    FreeAndNilIntf(i + 1)
print()
for i in range(count):
    InitialiseNilImpl(i + 1)
print()
for i in range(count):
    FreeAndNilImpl(i + 1)
于 2013-08-29T09:48:58.200 に答える
4

inheritedはい、このコードは機能しますが、個人的にはコンストラクタとデストラクタに追加する傾向があります。

このメカニズムを使用する実装を持つ多くのライブラリがあります。モバイル プラットフォーム用の最新の Delphi コンパイラは、自動参照カウントである ARC を使用してオブジェクトの有効期間を管理します。これは同じ手法ですが、オブジェクト参照のコンパイラの処理に組み込まれています。

于 2013-08-28T20:17:49.450 に答える
3

同じアイデアのわずかに異なる実装を次に示します。

unit ObjectGuard;

interface

type
  TObjectReference = ^TObject;

  { TObjectGuard }
  TObjectGuard = class(TInterfacedObject)
  private
    fUsed: integer;
    fObjectVariable: array [0..9] of TObjectReference;
  public
    constructor Create(var v0); overload;
    constructor Create(var v0, v1); overload;
// add overloaded constructors for 3,4,5... variables
    destructor Destroy; override;
  end;

implementation

constructor TObjectGuard.Create(var v0);
begin
  fObjectVariable[0] := @TObject(v0);
  Tobject(v0) := nil;
  fUsed := 1;
end;

constructor TObjectGuard.Create(var v0, v1);
begin
  fObjectVariable[0] := @TObject(v0);
  Tobject(v0) := nil;
  fObjectVariable[1] := @TObject(v1);
  Tobject(v1) := nil;
  fUsed := 2;
end;

destructor TObjectGuard.Destroy;
var
  i: integer;
begin
  for i := 0 to FUsed - 1 do
    if Assigned(fObjectVariable[i]^) then
    begin
      fObjectVariable[i]^.Free;
      fObjectVariable[i]^ := nil;
    end;
  inherited;
end;

end.

利点は、次のような単純な使用法です。

procedure Test;
var
  Guard: IInterface
  vStringList: TStringList;
  vForm: TForm;
begin
  Guard := TObjectGuard.Create(vStringList, vForm);
  vStringList := TStringList.Create;
  vForm:= TForm.Create(nil);
  // code that does something
end;

メソッドの先頭で Guard を作成し、1 回の呼び出しで任意の数の変数を渡すことができると便利です。したがって、最初にオブジェクト インスタンスを作成する必要はありません。

また、変数はコンストラクターで自動的に nil に初期化されることに注意してください。

編集: また、インターフェイスの有効期間はメソッドの実行時間と等しいため、それをプロファイリングに使用できます。おそらく、制御を容易にするために IFDEF を使用します。

于 2013-08-28T21:59:07.720 に答える
1

インターフェイスでデストラクタをラップする必要はありません。デフォルトでは、Delphi はインターフェースを使用する各プロシージャ/関数に舞台裏の try/finally を組み込みます。この場合、インターフェースの参照カウントが減少し、ゼロに達するとデストラクタが呼び出されます。

私は簡単にチェックしましたが、(少なくとも Delphi 7 では) 1 つのデストラクタで例外が発生すると、残念ながら他のデストラクタが停止します。これを停止する 1 つの方法は、各デストラクタに try/except を書き込むことですが、これは、そもそもコードを節約するためだけに別の場所にコードを追加することです...

type
  IMyIntf=interface(IInterface)
    function GetName:string;
    procedure SetName(const Name:string);
    property Name:string read GetName write SetName;
  end;

  TMyObj=class(TInterfacedObject, IMyIntf)
  private
    FName:string;
    function GetName:string;
    procedure SetName(const Name:string);
  public
    constructor Create(const Name:string);
    destructor Destroy; override;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  x,y:IMyIntf;
begin
  x:=TMyObj.Create('a');
  y:=TMyObj.Create('b');

  x.Name:='x';
  y.Name:='y';
end;

{ TMyObj }

constructor TMyObj.Create(const Name: string);
begin
  inherited Create;
  FName:=Name;
end;

destructor TMyObj.Destroy;
begin
  MessageBox(Application.Handle,PChar(FName),'test',MB_OK);
  //test: raise Exception.Create('Destructor '+FName);
  inherited;
end;

function TMyObj.GetName: string;
begin
  Result:=FName;
end;

procedure TMyObj.SetName(const Name: string);
begin
  FName:=Name;
end;
于 2013-08-28T21:07:46.883 に答える