【问题标题】:Shouldn't calling Free on an object reference set to nil throw an Access Violation every time it is called?每次调用设置为 nil 的对象引用时不应该调用 Free 引发访问冲突吗?
【发布时间】:2012-01-25 08:24:55
【问题描述】:

我收到来自单元 DBXCommon.pas(在 Delphi XE 中)的访问冲突。当我查看代码时,我看到如下内容(在感叹号处):

function TDBXConnectionFactory.GetConnection(const DBXContext: TDBXContext;
  const ConnectionProperties: TDBXProperties): TDBXConnection;
var
  ConnectionBuilder:  TDBXConnectionBuilder;
  DelegatePath:       TDBXDelegateItem;
  Connection:         TDBXConnection;
  CombinedProperties: TDBXProperties;
begin
  //...
  ConnectionBuilder := TDBXConnectionBuilder.Create;
  Connection        := nil;
  try
    //..lots of setting ConnectionBuilder properties
    ConnectionBuilder.FInputPassword := CombinedProperties[TDBXPropertyNames.Password];
    Connection := ConnectionBuilder.CreateConnection;
    Connection.Open;
    Result     := Connection;
!!  Connection := nil;
  finally
!!  Connection.Free;
    ConnectionBuilder.Free;
  end;
end;

但我在DBXCommon.pas 中看到更多这样的构造(首先分配 Nil,然后是 Free)。这是我不知道的一些构造,还是真的每次调用这段代码都会导致访问冲突?

【问题讨论】:

标签: delphi delphi-xe


【解决方案1】:

nil 引用上调用Free 是安全的,因为它在调用Destroy 之前会检查Self <> nil。请参阅 Allen Bauer 在 Embarcadero forum 为什么引入 TObject.Free 的解释。我在这里只包括相关的报价:

在 TObject 上引入非虚拟 Free 方法的唯一原因是在析构函数中用作以下的简单简写:

if FField <> nil then
  FField.Destroy;

【讨论】:

  • 现在还没有吸取教训吗?人们应该停止链接到 Embarcadero 网页。他们在那里只有2-3年。然后……噗噗。走了:) :)
【解决方案2】:

TObject.Free 基本实现为if Self &lt;&gt; nil then Destroy,所以上面的代码应该不会引发任何异常。

【讨论】:

    【解决方案3】:

    在空引用上调用Free 总是安全的。去看看TObject.Free的实现看看为什么。

    这段代码是一个工厂函数的例子。它的工作是创建一个类的新实例,但是如果它失败了,它需要确保它在抛出异常时不会泄漏一个半创建的实例,所以它调用Free。当它确定它会成功时,它会将结果的所有权转移给调用者。它仍然调用Free,但如果它已经转移了所有权,那么它最终会在空引用上调用Free,并且不会造成任何伤害。此代码用于转让所有权:

    Result := Connection;
    Connection := nil;
    

    I 编写工厂函数的方式将取消单独的Connection 变量。我会直接在Result 中构造结果,但如果有异常则释放它,如下所示:

    function TDBXConnectionFactory.GetConnection(const DBXContext: TDBXContext;
      const ConnectionProperties: TDBXProperties): TDBXConnection;
    var
      ConnectionBuilder:  TDBXConnectionBuilder;
      DelegatePath:       TDBXDelegateItem;
      Connection:         TDBXConnection;
      CombinedProperties: TDBXProperties;
    begin
      //...
      ConnectionBuilder := TDBXConnectionBuilder.Create;
      try
        //..lots of setting ConnectionBuilder properties
        ConnectionBuilder.FInputPassword := CombinedProperties[TDBXPropertyNames.Password];
        Result := ConnectionBuilder.CreateConnection;
        try
          Result.Open;
        except
          Result.Free;
          raise;
        end;
      finally
        ConnectionBuilder.Free;
      end;
    end;
    

    效果一样。

    【讨论】:

    • 谢谢,在我脑后的某个地方我知道这一点。但是,对于未经训练的人来说,按照您展示的方式编写它会更清楚。
    • +1 这是编写返回新对象的函数的惯用方式
    • 我同意 David 的观点,但如果您不重新引发异常,则应将 Result 设置为 nil。这是我们在性能关键代码中所做的事情,其中​​检查非零结果比引发和处理异常要快...
    • @Marjan,如果您不重新引发异常,那么您必须处理它们。没关系。修复导致引发异常的问题是一件非常好的事情。但是大多数代码无法修复所有(甚至任何)可能导致异常的问题。任何捕获异常但既不能解决问题也不能重新引发异常的代码都是错误的。
    • @RobKennedy:我没有争论 :)。我正在谈论的代码确实设置为尽其所能,尝试替代方案,或回滚它开始的任何内容。这是一个在内存中拉动 80+ 千兆字节的服务器,并且加载时间长达几个小时,我们不希望它仅仅因为某些东西不可用而崩溃......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-26
    • 1970-01-01
    • 1970-01-01
    • 2018-09-07
    • 2017-02-15
    • 1970-01-01
    • 2023-03-21
    相关资源
    最近更新 更多