Delphi FreeAndNil:寻找替代实现

H.H*_*ack 4 delphi memory-management object-lifetime rad-studio delphi-run-time-library

注意:请耐心等待,由于此处此处的一些讨论以及我在此处此处报告的一些问题,我感到有点“火爆”

一些背景

Ye olde(10.4 之前)FreeAndNil看起来像这样:

FreeAndNil(var SomeObject)
Run Code Online (Sandbox Code Playgroud)

新的和新鲜的FreeAndNil看起来是这样的:

FreeAndNil(const [ref] SomeObject: TObject);
Run Code Online (Sandbox Code Playgroud)

IMO 都有其缺点:

  • 旧的不做任何类型检查,所以调用FreeAndNil指针、记录和接口编译得很好,但在运行时会产生有趣但通常不需要的效果。(完全狂暴,或者如果幸运的话它会因 EAccessViolation、EInvalidOperation 等而停止。)
  • 新的接受一个 const 参数,因此任何对象。但是随后提供的对象指针实际上是使用一些古怪的代码更改的。
  • 你现在可以FreeAndNil像这样调用新的:FreeAndNil(TObject.Create)它会编译甚至运行得很好。我喜欢FreeAndNil在我出错时警告我并提供例如属性而不是字段的旧版本。不确定如果为此FreeAndNil实现提供对象类型属性会发生什么。没试。

如果我们将签名更改为 ,FreeAndNil(var SomeObject:TObject)那么它将不允许我们传递任何其他变量类型,然后正是该TObject类型。这也是有道理的,好像不是这样FreeAndNil,人们可以轻松地更改TComponent在例程中作为类型提供的变量,将 var 变量更改为完全不同类型的对象,例如TCollection. 当然FreeAndNil不会做这样的事情,因为它总是将 var 参数更改为 nil。

所以这是FreeAndNil一个特例。 甚至可能特别到足以说服 delphi 添加一个编译器魔术 FreeAndNil实现?投票给任何人?

潜在的解决方法

我想出了下面的代码作为替代方法(这里作为辅助方法,但也可以是实现的一部分TObject),它结合了两个世界。这Assert将有助于在运行时查找无效调用。

procedure TSGObjectHelper.FreeAndNilObj(var aObject);
begin
  if Assigned(self) then
  begin
    Assert(TObject(aObject)=self,ClassName+'.FreeAndNil Wrong parameter provided!');
    pointer(aObject):=nil;
    Destroy;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

用法是这样的:

var MyObj:=TSOmeObject.Create;
...
MyObj.FreeAndNilObj(MyObj);
Run Code Online (Sandbox Code Playgroud)

我实际上已经测试了这个例程,它甚至比 10.4FreeAndNil实现略快。我猜是因为我先做作业检查,然后Destroy直接打电话。我最喜欢的是:

  • 类型检查发生在运行时,然后仅当断言为 ON 时。
  • 感觉就像必须两次传递相同的变量。这不一定是正确的/必需的。它必须是同一个对象,并且参数必须是一个变量。

另一项调查

但是如果不带参数就可以调用不是很好吗

var MyObj:=TSomeObject.Create;
...
MyObj.FreeAndNil;
Run Code Online (Sandbox Code Playgroud)

所以我弄乱了self指针并设法将它设置为nil使用 10.4 在他们的FreeAndNil. 嗯......在方法内部起作用,self指向nil. 但是在这样调用之后FreeAndNil,MyObj 变量不是 nil,而是一个陈旧的指针。(这是我所期望的。)此外,MyObj可以是属性或(结果)例程、构造函数等。

所以没了在这里以及...

最后的问题:

你能想到一个更清洁/更好的解决方案或技巧:

  • FreeAndNil(var aObject:TObject) 具有不那么严格的类型检查编译时间(可能是编译器指令?),因此它允许编译和调用任何对象类型的变量。
  • 当传递的东西不是某种对象类型的变量/字段时,抱怨编译时间
  • 帮助描述RSP-29716 中的最佳解决方案/要求

Dal*_*kar 7

唯一正确的解决方案FreeAndNil是类型安全并且不允许释放函数结果和属性是泛型 var 参数:

 procedure FreeAndNil<T: class>(var Obj: T); inline;
Run Code Online (Sandbox Code Playgroud)

但是,目前 Delphi 编译器不允许在独立程序和函数上使用泛型https://quality.embarcadero.com/browse/RSP-13724

尽管如此,这并不意味着你不能有通用的FreeAndNil实现,只是它会比必要的更冗长。

type
  TObj = class
  public
    class procedure FreeAndNil<T: class>(var Obj: T); static; inline;
  end;

class procedure TObj.FreeAndNil<T>(var Obj: T);
var
  Temp: TObject;
begin
  Temp := Obj;
  Obj := nil;
  Temp.Free;
end;
Run Code Online (Sandbox Code Playgroud)

Rio 中引入的类型推断将允许您在不指定通用签名的情况下调用它:

TObj.FreeAndNil(Obj);
Run Code Online (Sandbox Code Playgroud)

FreeAndNil在旧的 Delphi 版本中调用(和使用)泛型也是可能的,但更冗长

TObj.FreeAndNil<TFoo>(Obj);
Run Code Online (Sandbox Code Playgroud)