这个问题是关于stackoverflow的人们特别评论的延续,我现在已经看过几次不同的时间了。我和教我Delphi的开发人员一样,为了保证安全,在释放对象之前,以及在做其他各种事情之前总是先检查if assigned()
。但是,我现在告诉我应该不添加此检查。我想知道如果我这样做,应用程序编译/运行的方式是否存在任何差异,或者它是否会对结果产生影响......
if assigned(SomeObject) then SomeObject.Free;
我们说我有一个表单,并且我会在表单创建时在后台创建一个位图对象,并在我完成它时释放它。现在我想我的问题是当我试图访问可能在某些时候可能免费的对象时,我已经习惯于对我的很多代码进行检查。即使没有必要,我也一直在使用它。我想要彻底......
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FBitmap: TBitmap;
public
function LoadBitmap(const Filename: String): Bool;
property Bitmap: TBitmap read FBitmap;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FBitmap:= TBitmap.Create;
LoadBitmap('C:\Some Sample Bitmap.bmp');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if assigned(FBitmap) then begin //<-----
//Do some routine to close file
FBitmap.Free;
end;
end;
function TForm1.LoadBitmap(const Filename: String): Bool;
var
EM: String;
function CheckFile: Bool;
begin
Result:= False;
//Check validity of file, return True if valid bitmap, etc.
end;
begin
Result:= False;
EM:= '';
if assigned(FBitmap) then begin //<-----
if FileExists(Filename) then begin
if CheckFile then begin
try
FBitmap.LoadFromFile(Filename);
except
on e: exception do begin
EM:= EM + 'Failure loading bitmap: ' + e.Message + #10;
end;
end;
end else begin
EM:= EM + 'Specified file is not a valid bitmap.' + #10;
end;
end else begin
EM:= EM + 'Specified filename does not exist.' + #10;
end;
end else begin
EM:= EM + 'Bitmap object is not assigned.' + #10;
end;
if EM <> '' then begin
raise Exception.Create('Failed to load bitmap: ' + #10 + EM);
end;
end;
end.
现在让我们说我引入了一个名为TMyList
TMyListItem
的新自定义列表对象。对于此列表中的每个项目,我当然必须创建/释放每个项目对象。有几种不同的创建项目的方法,以及一些不同的方法来销毁项目(添加/删除是最常见的)。我确信在这里提供这种保护是一种非常好的做法......
procedure TMyList.Delete(const Index: Integer);
var
I: TMyListItem;
begin
if (Index >= 0) and (Index < FItems.Count) then begin
I:= TMyListItem(FItems.Objects[Index]);
if assigned(I) then begin //<-----
if I <> nil then begin
I.DoSomethingBeforeFreeing('Some Param');
I.Free;
end;
end;
FItems.Delete(Index);
end else begin
raise Exception.Create('My object index out of bounds ('+IntToStr(Index)+')');
end;
end;
在许多场景中,至少我希望在我尝试释放它之前仍然创建该对象。但是你永远不知道在未来物体获得自由之前会发生什么样的滑倒。我总是使用这张支票,但现在我被告知我不应该这样做,而且我仍然不明白为什么。
修改
以下是一个尝试向您解释为什么我有这样做习惯的例子:
procedure TForm1.FormDestroy(Sender: TObject);
begin
SomeCreatedObject.Free;
if SomeCreatedObject = nil then
ShowMessage('Object is nil')
else
ShowMessage('Object is not nil');
end;
我的观点是if SomeCreatedObject <> nil
与if Assigned(SomeCreatedObject)
不同,因为在释放SomeCreatedObject
后,它不会评估为nil
。所以这两项检查都是必要的。
答案 0 :(得分:123)
这是一个非常广泛的问题,有许多不同的角度。
Assigned
函数的含义
您问题中的大部分代码都背叛了对Assigned
函数的错误理解。 documentation说明了这一点:
测试 nil (未分配)指针或程序变量。
使用已分配来确定指针或过程 P引用的是 nil 。 P必须是指针的变量引用或 程序类型。
已分配(P)对应于测试 P&lt;&gt; nil 表示指针变量 和 @P&lt;&gt;没有的程序变量。
已分配如果P nil 则返回 False ,否则返回 True 。
提示:在测试对象事件和分配程序时,您 无法测试 nil ,并且使用已分配是正确的方式。
...
注意:已分配无法检测到悬空指针 - 也就是说, nil ,但不再指向有效指针数据
Assigned
的含义因指针和程序变量而异。在本答案的其余部分中,我们将仅考虑指针变量,因为这是问题的上下文。请注意,对象引用实现为指针变量。
从文档中得到的关键点是,对于指针变量:
Assigned
相当于测试<> nil
。Assigned
无法检测指针或对象引用是否有效。在这个问题的背景下,这意味着
if obj<>nil
和
if Assigned(obj)
完全可以互换。
在致电Assigned
Free
TObject.Free
的实施非常特殊。
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;
这允许您在Free
的对象引用上调用nil
,这样做无效。对于它的价值,我知道RTL / VCL中没有其他地方可以使用这样的技巧。
您希望在Free
对象引用上调用nil
的原因源于构造函数和析构函数在Delphi中的运行方式。
当构造函数中引发异常时,将调用析构函数。这样做是为了释放在成功构造函数的那一部分中分配的任何资源。如果Free
未实现,那么析构函数必须如下所示:
if obj1 <> nil then
obj1.Free;
if obj2 <> nil then
obj2.Free;
if obj3 <> nil then
obj3.Free;
....
拼图的下一部分是Delphi constructors initialise the instance memory to zero。这意味着任何未分配的对象引用字段都是nil
。
将所有这些放在一起,析构函数代码现在变为
obj1.Free;
obj2.Free;
obj3.Free;
....
您应该选择后一个选项,因为它更具可读性。
有一种情况需要测试是否在析构函数中分配了引用。如果你需要在破坏它之前调用该对象的任何方法,那么显然你必须防止它被nil
的可能性。因此,如果它出现在析构函数中,此代码将冒AV的风险:
FSettings.Save;
FSettings.Free;
相反,你写
if Assigned(FSettings) then
begin
FSettings.Save;
FSettings.Free;
end;
在析构函数外部测试Assigned
你还谈到在析构函数之外编写防御性代码。例如:
constructor TMyObject.Create;
begin
inherited;
FSettings := TSettings.Create;
end;
destructor TMyObject.Destroy;
begin
FSettings.Free;
inherited;
end;
procedure TMyObject.Update;
begin
if Assigned(FSettings) then
FSettings.Update;
end;
在这种情况下,再次无需在Assigned
中测试TMyObject.Update
。原因是除非TMyObject.Update
的构造函数成功,否则您根本无法调用TMyObject
。如果TMyObject
的构造函数成功,那么您确定已分配FSettings
。因此,再次通过对Assigned
进行虚假调用,使代码更难以阅读,更难维护。
在某种情况下,您需要编写if Assigned
,这就是所讨论对象的存在是可选的。例如
constructor TMyObject.Create(UseLogging: Boolean);
begin
inherited Create;
if UseLogging then
FLogger := TLogger.Create;
end;
destructor TMyObject.Destroy;
begin
FLogger.Free;
inherited;
end;
procedure TMyObject.FlushLog;
begin
if Assigned(FLogger) then
FLogger.Flush;
end;
在这种情况下,该类支持两种操作模式,包括和不包含日志记录。决定是在构造时进行的,任何引用日志对象的方法都必须测试它的存在。
这种不常见的代码形式使得对Assigned
的非可选对象使用虚假调用变得更加重要。当您在代码中看到if Assigned(FLogger)
时,该代码应该清楚地表明该类可以在FLogger
不存在的情况下正常运行。如果你在代码周围向Assigned
发出无偿呼叫,那么你就无法一眼就知道某个对象是否应该始终存在。
答案 1 :(得分:21)
Free
有一些特殊逻辑:它会检查Self
是否为nil
,如果是,则返回时不做任何操作 - 因此您可以安全地调用{{1} }即使X.Free
是X
。当您编写析构函数时,这很重要--David在his answer中有更多详细信息。
您可以查看nil
的源代码,了解其工作原理。我不熟悉Delphi源代码,但它是这样的:
Free
或者,如果您愿意,可以将其视为使用Assigned
的等效代码:
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;
您可以编写自己的方法来检查procedure TObject.Free;
begin
if Assigned(Self) then
Destroy;
end;
,只要他们重新static (i.e., not virtual
or dynamic
) instance methods(感谢David Heffernan的文档链接)。但是在Delphi库中,if Self <> nil
是我所知道的唯一使用此技巧的方法。
因此,在致电Free
之前,您无需检查变量是否为Assigned
;它已经为你做到了。这就是为什么建议调用Free
而不是直接调用Free
的原因:如果您在Destroy
引用上调用了Destroy,则会出现访问冲突。
答案 2 :(得分:16)
为什么你不应该打电话
if Assigned(SomeObject) then
SomeObject.Free;
只是因为你会执行类似这样的事情
if Assigned(SomeObject) then
if Assigned(SomeObject) then
SomeObject.Destroy;
如果您只拨打SomeObject.Free;
,那就只是
if Assigned(SomeObject) then
SomeObject.Destroy;
对于您的更新,如果您害怕对象实例引用,请使用FreeAndNil。它会破坏和取消引用你的对象
FreeAndNil(SomeObject);
就像你打电话
一样SomeObject.Free;
SomeObject := nil;
答案 3 :(得分:-3)
我不完全确定,但似乎:
if assigned(object.owner) then object.free
工作正常。在这个例子中它将是
if assigned(FBitmap.owner) then FBitmap.free