Delphi FreeAndNil:寻找替代实现



注意:请耐心等待,我感觉有点"火焰烧烤">由于这里和这里的一些讨论以及我在这里和这里报道的一些问题。

一些背景

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

FreeAndNil(var SomeObject)

全新的FreeAndNil如下所示:

FreeAndNil(const [ref] SomeObject: TObject);

IMO都有缺点:

  • 旧版本不进行任何类型检查,因此在指针、记录和接口上调用FreeAndNil编译得很好,但在运行时会产生有趣但通常不需要的效果。(完全失控,或者如果幸运的话,它会因EAccessViolation、EInvalidOperation等而停止。)
  • 新的一个接受const参数,因此接受任何对象。但随后提供的对象指针实际上使用了一些古怪的代码进行了更改
  • 现在可以这样调用新的FreeAndNilFreeAndNil(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;

用法如下:

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

实际上,我已经测试了这个例程,它甚至比10.4FreeAndNil的实现稍微快一些。我想是因为我先做分配检查,然后直接调用Destroy。我所做的喜欢的是:

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

另一项调查

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

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

因此,我篡改了self指针,并使用10.4在其FreeAndNil中使用的相同的Hacky-Wacky代码将其设置为nil。好在方法内部起作用的self指向nil。但是在这样调用FreeAndNil之后,MyObj变量不是nil,而是一个过时的指针。(这正是我所期望的。)此外,MyObj可以是一个属性,也可以是例程、构造函数等的结果。

所以nope也在这里。。。

最后一个问题是:

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

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

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);

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

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;

并且从CCD_ 32单元移除当前(10.4及更早)CCD_。

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

FreeAndNil(SomeObjectVariable)

和10.3+类型推理避免了必须写入:

FreeAndNil<TMyClassSpec>(SomeObjectVariable)

这很好,因为您的大多数代码都会很好地编译,而不会发生任何更改。

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

TObject.FreeAndNil(SomeObjectVariable)

这对我来说是可以接受的,而且比当前和历史上使用FreeAndNil(const [ref] aObject:TObject)或非类型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;
...

最新更新