【问题标题】:How to safely Create and Free multiple objects in Delphi如何在 Delphi 中安全地创建和释放多个对象
【发布时间】:2011-10-04 05:31:27
【问题描述】:

您应该如何安全地创建和释放多个对象?

基本上就是这样的事情:

  newOrderSource := TWebNewOrderSource.Create();
  twData := TTWData.Create();
  webData := TWebData.Create();

  try
    //do stuff
  finally
    newOrderSource.Free();
    twData.Free();
    webData.Free();
  end;

在这种情况下,第二个和第三个创建命令是不安全的,因为它们与数据库一起使用。我是否应该将所有 Creates 放在 try 块中,并在调用 free 之前检查它们是否已分配?

【问题讨论】:

    标签: delphi


    【解决方案1】:

    如果您首先将 nil 分配给变量,则可以使用一个 try 块来执行此操作,例如,

    newOrderSource := nil;
    twData := nil;
    webData := nil;
    try
      newOrderSource := TWebNewOrderSource.Create();    
      twData := TTWData.Create();    
      webData := TWebData.Create();    
    
      //do stuff    
    finally    
      webData.Free();    
      twData.Free();    
      newOrderSource.Free();    
    end;    
    

    这是因为Free() 检查Self 中的nil

    【讨论】:

    • 我发布了一个非常相似的答案,一个小的变化是在尝试之前放置 newOrderSource := TWebNewOrderSource.Create();,并放弃 newOrderSource := nil;
    • 这也有效,并且删除了一个不需要的分配,但在这种情况下我更喜欢简单性和一致性,因为如果“do stuff”调用数据库,保存分配不太可能产生很大的不同。跨度>
    • 我一直都在使用它,但为了安全起见,您应该注意,与 OP 的原始代码相比,这可以保护您免受任何构造函数中引发的异常,但 not释放对象时引发的异常。如果您对此感到偏执,您唯一的选择是拥有与对象一样多的 try/finally(或者让它们实现接口并让它们超出范围)。
    • @Leonardo 如果在构造函数中引发异常,则运行析构函数以便可以整理部分构造函数对象。当这种情况发生时,任何未创建的对象成员都将是nil,因为 Delphi 将所有内存初始化为零,这恰好与 nil 相同。这就是为什么您不需要使用if Assigned() 来保护对 Free 的每个调用。这也是为什么你叫 Free 而不是 Destroy 的原因。
    • @Leonardo Free 不是虚拟方法,因此编译器生成一个普通函数调用,将Self 作为参数传递。这就像将nil 作为接受TObject 的过程的第一个参数传递,只是看起来有点古怪。
    【解决方案2】:

    相信大家都知道,管理对象的标准方式是这样的:

    A := TMyObject.Create;
    try
      A.DoSomething;
    finally
      A.Free;
    end;
    

    如果TMyObject.Create 中存在异常,则将调用析构函数,然后引发异常。在这种情况下,A 将不会被分配给。

    当您有多个对象时,您可以重复该模式:

    A := TMyObject.Create;
    try
      B := TMyObject.Create;
      try
        A.DoSomething;
        B.DoSomething;
      finally
        B.Free;
      end;
    finally
      A.Free;
    end;
    

    这很快就会变得一团糟,因此问题就来了。

    一个标准技巧是利用Free 可以安全地在nil 对象引用上调用这一事实。

    A := nil;
    B := nil;
    try
      A := TMyObject.Create;
      B := TMyObject.Create;
      A.DoSomething;
      B.DoSomething;
    finally
      B.Free;
      A.Free;
    end;
    

    这确实有一个小弱点,即它对B.Free 中引发的异常没有弹性,但将其视为可以忽略的失败条件并非没有道理。析构函数不应引发异常。如果他们这样做了,那么您的系统可能已经不可挽回地损坏了。

    随着更多对象的添加,上述模式可能会变得有点混乱,因此我个人使用以下辅助方法。

    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(A, B);
    try
      A := TMyObject.Create;
      B := TMyObject.Create;
      A.DoSomething;
      B.DoSomething;
    finally
      FreeAndNil(B, A);
    end;
    

    【讨论】:

    • 难道你不能让你的辅助方法接收一个值数组,这样你就可以向它们传递任意数量的对象吗?
    • @Jerry 这也可以,但我现在不喜欢使用开放数组语法。我的理由是这些函数只编写一次,从未修改过。因此,与修改的活代码相比,我不太关心重复。这样做的目的是为了让调用代码更简洁,也就是被修改的代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-28
    • 2011-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多