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.
现在让我们说,我引入一个新的自定义列表对象TMyListItem TMyList。对于这个列表中的每个项目,当然我必须创建/释放每个项目对象。有几种不同的方式创建一个项目,以及一些不同的方式来销毁一个项目(添加/删除是最常见的)。我相信这是一个很好的做法,把这种保护在这里…
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;
我的观点是如果SomeCreatedObject<> nil与Assigned(SomeCreatedObject)不同,因为释放SomeCreatedObject后,它不会计算为nil。所以两个检查都应该是必要的。
解决方法
Assigned函数的含义
你的问题中的大部分代码都错误地理解了Assigned函数。 documentation说:
Tests for a nil (unassigned) pointer or procedural variable.
Use
Assigned to determine whether the pointer or procedure referenced by P
is nil. P must be a variable reference of a pointer or procedural
type. Assigned(P) corresponds to the test P<> nil for a pointer
variable,and @P <> nil for a procedural variable.Assigned returns
False if P is nil,True otherwise.Note: Assigned cannot detect a
dangling pointer–that is,one that is not nil but no longer points to
valid data. For example,in the code example for Assigned,Assigned
does not detect that P is not valid.
从这里的关键点是:
> Assigned等价于测试<>零。
> Assigned无法检测指针或对象引用是否有效。
在这个问题的上下文中这是什么意思
if obj<>nil
和
if Assigned(obj)
是完全可互换的。
测试在调用Free之前分配
TObject.Free的实现非常特殊。
procedure TObject.Free; begin if Self <> nil then Destroy; end;
这允许你在一个为nil的对象引用上调用Free,这样做没有任何效果。对于什么是值得的,我知道在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.这意味着任何未分配的对象引用字段为零。
obj1.Free; obj2.Free; obj3.Free; ....
你应该选择后一个选项,因为它更容易阅读。
有一种情况,您需要测试参考是否在析构函数中分配。如果你需要调用对象上的任何方法,在销毁它之前,然后清楚你必须防止它的可能性为零。所以这个代码将运行AV的风险,如果它出现在析构函数中:
FSettings.Save; FSettings.Free;
相反你写
if Assigned(FSettings) then begin FSettings.Save; FSettings.Free; end;
测试在析构函数外部分配
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;
在这种情况下,再次不需要在TMyObject.Update中测试Assigned。原因是你根本不能调用TMyObject.Update,除非TMyObject的构造函数成功。如果TMyObject的构造函数成功,那么你肯定知道分配了FSettings。所以再次,你让你的代码更少的可读性和更难维护通过对Assigned的虚假调用。
有一种情况,你需要写如果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的伪调用。当你看到代码中的Assigned(FLogger)时,应该清楚地表明类可以正常运行,而FLogger不存在。如果你对你的代码进行免费的调用,那么你就失去了一个对象总是存在的能力。