Delphi 执行嵌套TRY/FINALLY语句的最佳实践

Delphi 执行嵌套TRY/FINALLY语句的最佳实践,delphi,memory-management,object-lifetime,try-finally,Delphi,Memory Management,Object Lifetime,Try Finally,您好,在delphi中执行嵌套的try&finally语句的最佳方法是什么 var cds1 : TClientDataSet; cds2 : TClientDataSet; cds3 : TClientDataSet; cds4 : TClientDataSet; begin cds1 := TClientDataSet.Create(application ); try cds2 := TClientDataSet.Crea

您好,在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;

你能推荐一种更好的方法吗?

以下几点如何:

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;
这使它保持紧凑,并且仅尝试释放已创建的实例。实际上不需要执行嵌套,因为任何失败都会导致跳转到finally并执行您提供的示例中的所有清理

就我个人而言,我尽量不使用相同的方法进行嵌套。。。例外情况是try/try/except/finally场景。如果我发现自己需要嵌套,那么对我来说,这是一个考虑重构到另一个方法调用的好时机

编辑得益于和的评论,这篇文章有点干净了


EDIT将对象创建更改为“不引用应用程序”,因为在本例中不需要这样做。

我将使用以下内容:

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;
var
  MyObject: TMyObject;

begin
  MyObject.Init;
  try
    /// ...
  finally
    MyObject.Done;
  end;
end;
有关接口的实现,请参阅本文,但您可以自己轻松创建类似的东西

编辑:

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

编辑2:


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

@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
  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;
var
  MyObject: TMyObject;

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

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

没有嵌套try的代码还有另一种变体。。。我终于想到了。如果您不创建构造函数的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;
我非常相信小代码(如果它不是太模糊的话)。

有一个很好的视频

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

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块中谈到了处理异常的问题。

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

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;

您需要使用nil初始化cds2、cds3和cds4,否则,如果创建cds1失败,在释放(无效)对象时将获得AVs。当用作堆栈变量时,只能假设字符串和接口指针为空。这真是太糟糕了!很难再远离最佳实践:1。如果构造函数中出现异常,Create必须始终放在try/finally-free之前-初始化为nil以防止出现这种情况,这只是一个丑陋的攻击。2.您可以传递一个非零所有者来创建对象,也可以自己释放对象-决不能两者都传递!我使用自己的InitialiseNil例程处理这个问题,该例程接受多个参数,而FreeAndNil例程也接受多个参数。这会使上面的代码读得更好一些。这不好。如果前三次释放中的一次出现异常,则内存泄漏。很好的实现,+1供使用,但它完全消除了try finally,这对于本例来说可能已经足够好了。我看到try。。。最后,只需要一根拐杖,因为在Delphi中,没有接口,RAII是不可能实现的。如果Delphi有堆栈分配的对象,那么就不需要尝试了。。。最后尽管如此,它仍然可以与保护接口一起使用。@mghie:除非析构函数关闭句柄或释放一些其他资源,这是非常常见的情况。@mghie:如果它们将被调用,那么是的。我还以为你们说的是“万一发生什么事”就把堆栈展开。另一方面,析构函数不能“从不抛出异常”。你并不总是知道什么时候会抛出异常,毕竟这是一个异常。@hiver:如果你按照文章的链接,你会看到析构函数将被调用。虽然析构函数可以抛出异常,但最好不要这样做(至少在与VCL生存期管理一起使用的
TComponent
子体中)。内存泄漏和/或崩溃是不可避免的后果。抱歉,我不太清楚。自动析构函数调用正是我想要的,不管实例是堆栈分配的,还是超出范围的,或者它是否包含在另一个对象中,并且在父对象被销毁时调用所包含对象的析构函数。正如C++一样,不要使用旧的样式对象。它们不能很好地处理继承,并且不支持过去十年中引入的任何Delphi类型,例如长字符串、接口或动态数组。@Rob:旧的stlye对象是什么意思?托宾?什么是最好的选择?@Luke:一个老式的对象是用TMyObject=object创建的,而不是用TMyObject=class甚至TMyObject=class(TObject)。旧式对象已被弃用很长一段时间了,它们只是t