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 等而停止。)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直接打电话。我最不喜欢的是:
另一项调查
但是如果不带参数就可以调用不是很好吗
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 参数:
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)