Delphi FreeAndNil:寻找替代实施
Delphi FreeAndNil: Looking for an alternate implementation
注意:请耐心等待,由于对 here and here and some issues I reported here and here.
的一些讨论,我觉得有点 “火焰烤”
一些背景
Ye olde(10.4 之前)FreeAndNil
看起来像这样:
FreeAndNil(var SomeObject)
新鲜的FreeAndNil
长这样:
FreeAndNil(const [ref] SomeObject: TObject);
IMO 都有其缺点:
- 旧版本不进行任何类型检查,因此在指针、记录和接口上调用
FreeAndNil
编译得很好,但在 运行 期间会产生有趣但通常不需要的效果。 (完全发疯,或者如果你幸运的话,它会因 EAccessViolation、EInvalidOperation 等而停止)
- 新的接受一个常量参数,因此接受任何对象。但是随后使用一些 hacky-wacky 代码 .
实际上更改了提供的对象指针
- 您现在可以像这样调用新的
FreeAndNil
:FreeAndNil(TObject.Create)
它会编译,甚至 运行 都可以。我喜欢旧的 FreeAndNil
,它在我出错时警告我并提供例如a 属性 而不是字段。不确定如果您向此 FreeAndNil
实现提供对象类型 属性 会发生什么。没试过。
如果我们将签名更改为 FreeAndNil(var SomeObject:TObject)
那么它将不允许我们传递任何其他变量类型,而恰恰是 TObject
类型。这也是有道理的,就好像它不是 FreeAndNil
一样,可以很容易地更改例程中作为类型 TComponent
提供的变量,将 var 变量更改为完全不同类型的对象,例如TCollection
。当然 FreeAndNil
不会做这样的事情,因为它总是将 var 参数更改为 nil。
所以这使得 FreeAndNil
成为一个特例。
甚至可能特别到足以说服 delphi 添加 编译器魔术 FreeAndNil
实现?有人投票吗?
可能的解决方法
我想出了下面的代码作为替代方法(这里作为辅助方法,但也可以是 TObject
实现的一部分),kind-a 结合了两个世界。 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
。
我所做的不是这样的:
- 类型检查在 运行 时间内发生,然后仅当断言打开时。
- 感觉就像必须将同一个变量传递两次一样。这不一定是 true/required。必须是同一个对象,参数必须是变量。
再次调查
但是如果没有参数就可以调用是不是很棒
var MyObj:=TSomeObject.Create;
...
MyObj.FreeAndNil;
所以我弄乱了 self
指针,并设法使用 10.4 在其 FreeAndNil
中使用的相同 Hacky-Wacky 代码将其设置为 nil
。嗯...在 方法中起作用,self
指向 nil
。但是在像这样调用 FreeAndNil
之后,MyObj 变量不是 nil,而是一个过时的指针。 (这是我所期望的。)此外,MyObj
可以是 属性 或例程、构造函数等的(结果)
所以不这里也...
最后是问题:
你能想到一个 cleaner/better 解决方案或技巧吗:
- FreeAndNil(var aObject:TObject) 编译时类型检查不那么严格(也许是编译器指令?)所以它允许编译和调用 any[=102= 的变量] 对象类型。
- 当传递的东西不是某个对象类型variable/field
时抱怨编译时间
- 帮助描述 RSP-29716
中什么是最好的 solution/requirement
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 class 的方法。 (由 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;
并将当前(10.4 及更早版本)FreeAndNil
从 sysutils
单元中删除以避免歧义。
当从 任何 其他方法中调用新的通用 FreeAndNil
方法时,只需调用:
FreeAndNil(SomeObjectVariable)
和 10.3+ 类型推断避免了必须写:
FreeAndNil<TMyClassSpec>(SomeObjectVariable)
这很好,因为 您的大部分代码无需更改即可编译。
在其他一些地方,例如全局例程和 initialization / finalization
部分必须调用:
TObject.FreeAndNil(SomeObjectVariable)
这对我来说是可以接受的,并且比使用 FreeAndNil(const [ref] aObject:TObject)
或无类型 FreeAndNil(var aObject)
的当前和历史中途解决方案要好得多
并且由于该例程非常简单并且性能似乎是一个问题,因此可以争辩为其使用汇编程序实现。虽然我不确定这是否是通用的 allowed/possible,(最好是 inline)方法。
FTM:也可以只保留 FreeAndNil(var aObject:TObject)
并告诉人们进行如下所示的类型转换,这也避免了编译器抱怨 var 类型。但是在这种情况下,可能要调整很多源代码。另一方面,它节省了代码膨胀,仍然避免了无效使用函数结果、属性或无效类型(如记录和指针)作为 FreeAndNil
的参数,并且对 change/implement.[=24= 非常简单]
...
var Obj:=TSomeObject.Create;
try
DoSOmethingUseFulWithObj(Obj);
finally
FreeAndNil(TObject(Obj)); // typecast avoids compiler complaining. Compiler wont allow invalid typecasts
end;
...
注意:请耐心等待,由于对 here and here and some issues I reported here and here.
的一些讨论,我觉得有点 “火焰烤”一些背景
Ye olde(10.4 之前)FreeAndNil
看起来像这样:
FreeAndNil(var SomeObject)
新鲜的FreeAndNil
长这样:
FreeAndNil(const [ref] SomeObject: TObject);
IMO 都有其缺点:
- 旧版本不进行任何类型检查,因此在指针、记录和接口上调用
FreeAndNil
编译得很好,但在 运行 期间会产生有趣但通常不需要的效果。 (完全发疯,或者如果你幸运的话,它会因 EAccessViolation、EInvalidOperation 等而停止) - 新的接受一个常量参数,因此接受任何对象。但是随后使用一些 hacky-wacky 代码 . 实际上更改了提供的对象指针
- 您现在可以像这样调用新的
FreeAndNil
:FreeAndNil(TObject.Create)
它会编译,甚至 运行 都可以。我喜欢旧的FreeAndNil
,它在我出错时警告我并提供例如a 属性 而不是字段。不确定如果您向此FreeAndNil
实现提供对象类型 属性 会发生什么。没试过。
如果我们将签名更改为 FreeAndNil(var SomeObject:TObject)
那么它将不允许我们传递任何其他变量类型,而恰恰是 TObject
类型。这也是有道理的,就好像它不是 FreeAndNil
一样,可以很容易地更改例程中作为类型 TComponent
提供的变量,将 var 变量更改为完全不同类型的对象,例如TCollection
。当然 FreeAndNil
不会做这样的事情,因为它总是将 var 参数更改为 nil。
所以这使得 FreeAndNil
成为一个特例。
甚至可能特别到足以说服 delphi 添加 编译器魔术 FreeAndNil
实现?有人投票吗?
可能的解决方法
我想出了下面的代码作为替代方法(这里作为辅助方法,但也可以是 TObject
实现的一部分),kind-a 结合了两个世界。 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
。
我所做的不是这样的:
- 类型检查在 运行 时间内发生,然后仅当断言打开时。
- 感觉就像必须将同一个变量传递两次一样。这不一定是 true/required。必须是同一个对象,参数必须是变量。
再次调查
但是如果没有参数就可以调用是不是很棒
var MyObj:=TSomeObject.Create;
...
MyObj.FreeAndNil;
所以我弄乱了 self
指针,并设法使用 10.4 在其 FreeAndNil
中使用的相同 Hacky-Wacky 代码将其设置为 nil
。嗯...在 方法中起作用,self
指向 nil
。但是在像这样调用 FreeAndNil
之后,MyObj 变量不是 nil,而是一个过时的指针。 (这是我所期望的。)此外,MyObj
可以是 属性 或例程、构造函数等的(结果)
所以不这里也...
最后是问题:
你能想到一个 cleaner/better 解决方案或技巧吗:
- FreeAndNil(var aObject:TObject) 编译时类型检查不那么严格(也许是编译器指令?)所以它允许编译和调用 any[=102= 的变量] 对象类型。
- 当传递的东西不是某个对象类型variable/field 时抱怨编译时间
- 帮助描述 RSP-29716 中什么是最好的 solution/requirement
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 class 的方法。 (由 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;
并将当前(10.4 及更早版本)FreeAndNil
从 sysutils
单元中删除以避免歧义。
当从 任何 其他方法中调用新的通用 FreeAndNil
方法时,只需调用:
FreeAndNil(SomeObjectVariable)
和 10.3+ 类型推断避免了必须写:
FreeAndNil<TMyClassSpec>(SomeObjectVariable)
这很好,因为 您的大部分代码无需更改即可编译。
在其他一些地方,例如全局例程和 initialization / finalization
部分必须调用:
TObject.FreeAndNil(SomeObjectVariable)
这对我来说是可以接受的,并且比使用 FreeAndNil(const [ref] aObject:TObject)
或无类型 FreeAndNil(var aObject)
并且由于该例程非常简单并且性能似乎是一个问题,因此可以争辩为其使用汇编程序实现。虽然我不确定这是否是通用的 allowed/possible,(最好是 inline)方法。
FTM:也可以只保留 FreeAndNil(var aObject:TObject)
并告诉人们进行如下所示的类型转换,这也避免了编译器抱怨 var 类型。但是在这种情况下,可能要调整很多源代码。另一方面,它节省了代码膨胀,仍然避免了无效使用函数结果、属性或无效类型(如记录和指针)作为 FreeAndNil
的参数,并且对 change/implement.[=24= 非常简单]
...
var Obj:=TSomeObject.Create;
try
DoSOmethingUseFulWithObj(Obj);
finally
FreeAndNil(TObject(Obj)); // typecast avoids compiler complaining. Compiler wont allow invalid typecasts
end;
...