4

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

一些背景

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

FreeAndNil(var SomeObject)

新的和新鲜的FreeAndNil样子是这样的:

FreeAndNil(const [ref] SomeObject: TObject);

IMO 都有其缺点:

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

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

所以这是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;

用法将是这样的:

var MyObj:=TSOmeObject.Create;
...
MyObj.FreeAndNilObj(MyObj);

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

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

另一项调查

但是如果一个人可以在没有参数的情况下调用,那不是很好吗

var MyObj:=TSomeObject.Create;
...
MyObj.FreeAndNil;

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

所以这里也不行……

最后是问题:

您能想出一个更清洁/更好的解决方案或技巧吗:

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

2 回答 2

7

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

 procedure FreeAndNil<T: class>(var Obj: T); inline;

但是,目前 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;

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

TObj.FreeAndNil(Obj);

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

TObj.FreeAndNil<TFoo>(Obj);
于 2020-06-20T09:30:27.717 回答
1

因为我们无法创建全局procedure FreeAndNil<T:class>(var aObject:T),所以我建议将下面的代码作为 TObject 类的方法。(由 embarcadero 进行 rtl 更改,但不需要更改编译器)

class procedure TObject.InternalFreeAndNil(var Object:TObject); static; // strict private class method
begin
  if Assigned(Object) then
  begin
    var tmp:=Object;
    Object:=nil;
    tmp.Destroy;
  end;
end;


class procedure TObject.FreeAndNil<T:class>(var Object:T); inline; // public generic class method
begin
  InternalFreeAndNil(TObject(Object));
end;

FreeAndNil并从单元中删除当前(10.4 和更早版本)sysutils以避免歧义。

FreeAndNil任何其他方法中调用新的泛型方法时,可以简单地调用:

FreeAndNil(SomeObjectVariable)

和 10.3+ 类型推断避免了编写:

FreeAndNil<TMyClassSpec>(SomeObjectVariable)

这很好,因为您的大部分代码无需更改即可很好地编译。

在其他一些地方,例如全局例程和initialization / finalization部分,必须调用:

TObject.FreeAndNil(SomeObjectVariable)

这对我来说是可以接受的,并且比当前和历史中使用 aFreeAndNil(const [ref] aObject:TObject)或 untyped的中途解决方案要好得多FreeAndNil(var aObject)

而且由于该例程非常简单,而且性能似乎是一个问题,因此有人可能会争辩说有一个汇编程序实现它。虽然我不确定这对于通用(最好是inline)方法是否允许/可能。

FTM:也可以只保留FreeAndNil(var aObject:TObject)并告诉人们进行如下所示的类型转换,这也可以避免编译器抱怨 var 类型。但是在这种情况下,可能需要调整很多源代码。另一方面,它节省了代码膨胀,仍然避免了函数结果、属性或无效类型(如记录和指针)作为参数的无效使用FreeAndNil,并且非常易于更改/实现。

...
var Obj:=TSomeObject.Create;
try
   DoSOmethingUseFulWithObj(Obj);
finally
  FreeAndNil(TObject(Obj)); // typecast avoids compiler complaining. Compiler wont allow invalid typecasts
end;
...
于 2020-06-21T21:33:58.890 回答