20

嗨,在 delphi 中做嵌套 try 和 finally 语句的最佳方法是什么?

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := TClientDataSet.Create(application );
  try
    cds2      := TClientDataSet.Create(application );
    try
      cds3      := TClientDataSet.Create(application );
      try
        cds4      := TClientDataSet.Create(application );
        try
        ///////////////////////////////////////////////////////////////////////
        ///      DO WHAT NEEDS TO BE DONE
        ///////////////////////////////////////////////////////////////////////
        finally
          cds4.free;
        end;

      finally
        cds3.free;
      end;
    finally
      cds2.free;
    end;
  finally
    cds1.free;
  end;
end;

你能提出一个更好的方法吗?

4

6 回答 6

31

以下内容如何:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil;
  cds3      := Nil;
  cds4      := Nil;
  try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);
    cds3      := TClientDataSet.Create(nil);
    cds4      := TClientDataSet.Create(nil);
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds4);
    freeandnil(cds3);
    freeandnil(cds2);
    freeandnil(Cds1);
  end;
end;

这使它保持紧凑,并且只尝试释放已创建的实例。确实没有必要执行嵌套,因为任何失败都会导致最终掉到最后并执行您提供的示例中的所有清理。

就我个人而言,我尽量不要嵌套在同一个方法中……除了 try/try/except/finally 场景。如果我发现自己需要嵌套,那么对我来说这是考虑重构为另一个方法调用的好时机。

编辑感谢mghieutku的评论,清理了一下。

EDIT将对象创建更改为不引用应用程序,因为在此示例中没有必要。

于 2008-12-29T17:25:20.877 回答
18

我会使用这样的东西:

var
  Safe: IObjectSafe;
  cds1 : TClientDataSet;
  cds2 : TClientDataSet;
  cds3 : TClientDataSet;
  cds4 : TClientDataSet;
begin
  Safe := ObjectSafe;
  cds1 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds2 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds3 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds4 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  ///////////////////////////////////////////////////////////////////////
  ///      DO WHAT NEEDS TO BE DONE
  ///////////////////////////////////////////////////////////////////////

  // if Safe goes out of scope it will be freed and in turn free all guarded objects
end;

对于接口的实现,请参阅这篇文章,但您可以轻松地自己创建类似的东西。

编辑:

我刚刚注意到在链接的文章中 Guard() 是一个过程。在我自己的代码中,我重载了返回 TObject 的 Guard() 函数,上面的示例代码假设类似。当然,现在可以使用泛型更好的代码......

编辑2:

如果您想知道为什么 try ... finally 在我的代码中被完全删除:如果不引入内存泄漏(当析构函数引发异常)或访问冲突的可能性,就不可能删除嵌套块。因此最好使用辅助类,让接口的引用计数完全接管。帮助类可以释放它保护的所有对象,即使某些析构函数引发异常。

于 2008-12-29T17:38:47.743 回答
4

没有嵌套尝试的代码还有另一种变体……我终于想到了。如果您不创建将构造函数的 AOWner 参数设置为 nil 的组件,那么您可以简单地使用 VCL 免费为您提供的生命周期管理:

var
  cds1: TClientDataSet;
  cds2: TClientDataSet;
  cds3: TClientDataSet;
  cds4: TClientDataSet;
begin
  cds1 := TClientDataSet.Create(nil);
  try
    // let cds1 own the other components so they need not be freed manually
    cds2 := TClientDataSet.Create(cds1);
    cds3 := TClientDataSet.Create(cds1);
    cds4 := TClientDataSet.Create(cds1);

    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////

  finally
    cds1.Free;
  end;
end;

我是小代码的忠实信徒(如果它不是太混淆的话)。

于 2008-12-30T09:29:59.383 回答
3

如果您想走这条(IMO)丑陋的路线(初始化为 nil 的组处理以了解是否需要释放),您至少必须保证您不会让析构函数中的异常阻止释放其余部分你的对象。
就像是:

function SafeFreeAndNil(AnObject: TObject): Boolean;
begin
  try
    FreeAndNil(AnObject);
    Result :=  True;
  except
    Result := False;
  end;
end;

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    IsOK1 : Boolean;
    IsOK2 : Boolean;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    IsOk2 := SafeFreeAndNil(cds2);    // an error in freeing cds2 won't stop execution
    IsOK1 := SafeFreeAndNil(Cds1);
    if not(IsOk1 and IsOk2) then
      raise EWhatever....
  end;
end;
于 2009-03-23T09:59:09.060 回答
2

关于构造函数和析构函数中的异常有一个很好的视频

它展示了一些很好的例子,例如:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds2);    //// what has if there in an error in the destructor of cds2
    freeandnil(Cds1);
  end;
end;

如果 cds2 的析构函数出错了怎么办

Cds1 不会被破坏

编辑

另一个很好的资源是:

Jim McKeeth 关于代码范围 III 中延迟异常处理的精彩视频,他谈到了在 finally 块中处理异常的问题。

于 2009-01-10T10:05:33.483 回答
1

@mghie:Delphi 有堆栈分配的对象:

type
  TMyObject = object
  private
    FSomeField: PInteger;
  public
    constructor Init;
    destructor Done; override;
  end;

constructor TMyObject.Init;
begin
  inherited Init;
  New(FSomeField);
end;

destructor TMyObject.Done;
begin
  Dispose(FSomeField);
  inherited Done;
end;

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  /// ...
end;

不幸的是,如上例所示: 堆栈分配的对象并不能防止内存泄漏。

所以这仍然需要像这样调用析构函数:

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  try
    /// ...
  finally
    MyObject.Done;
  end;
end;

好的,我承认,这几乎是题外话,但我认为在这种情况下它可能会很有趣,因为堆栈分配的对象被提到作为一种解决方案(如果没有自动析构函数调用,它们就不是)。

于 2008-12-29T18:54:00.137 回答