为什么在使用或释放​​事物之前不应该使用“如果已分配()”?

这个问题是我在现在看到几个不同时代的人们对于计算器的特别评论的延续。 我和那些教我Delphi的开发者一起,为了保持安全,在释放对象之前,以及在做其他各种事情之前,总是先检查if assigned() 。 不过,我现在被告知我应该添加这个检查。 我想知道是否应用程序编译/运行如果我这样做,或者如果它不会影响结果的任何差异…

 if assigned(SomeObject) then SomeObject.Free; 

假设我有一个表单,并且在创build表单时在后台创build一个位图对象,并在完成后释放它。 现在我想我的问题是,当我尝试访问可能在某些时候可能已被释放的对象时,我习惯于将这个检查放在很多代码上。 即使没有必要,我也一直在使用它。 我喜欢彻底…

 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. 

现在,让我们来介绍一个名为TMyListTMyListItem的新自定义列表对象。 对于这个列表中的每个项目,当然我必须创build/释放每个项目对象。 创build项目有几种不同的方法,以及一些销毁项目的不同方法(添加/删除是最常见的)。 我相信这是一个很好的做法,把这个保护在这里…

 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; 

在很多情况下,至less我希望在我释放它之前,这个对象仍然是被创build的。 但是,你永远不知道在将来一个对象被释放之前可能发生什么滑动。 我一直使用这个检查,但现在我被告知我不应该,我仍然不明白为什么。


编辑

下面是一个例子,试图向你解释为什么我有这样做的习惯:

 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 。 所以这两个检查应该是必要的。

这是一个非常广泛的问题,有很多不同的angular度。

Assigned函数的含义

你问题中的大部分代码都背离了对Assigned函数的错误理解。 该文件指出:

testing一个零(未分配)指针或程序variables。

使用Assigned来确定P引用的指针或过程是否为零。 P必须是指针或过程types的variables引用。 Assigned(P)对应于指针variables的testingP <> nil,对于过程variables,P <> nil。

如果P为零,则赋值返回False,否则为True。

注意 :Assigned不能检测悬挂指针 – 也就是说,不是零,而是不再指向有效数据。 例如,在分配的代码示例中,分配不会检测到P无效。

从这个关键点是:

  1. Assigned等同于testing<> nil
  2. Assigned不能检测指针或对象引用是否有效。

这个问题的意思是这个意思

 if obj<>nil 

 if Assigned(obj) 

是完全可以互换的。

testing在调用Free之前Assigned

TObject.Free的实现是非常特殊的。

 procedure TObject.Free; begin if Self <> nil then Destroy; end; 

这允许你在一个对象引用上调用Free ,而这个对象是无效的。 对于它的价值,我知道RTL / VCL中没有其他地方使用这种技巧。

为什么你想允许Free被调用的nil对象引用源于构造函数和析构函数在Delphi中的运行方式。

当构造函数中引发exception时,将调用析构函数。 这是为了解除分配在构造函数成功部分中分配的所有资源。 如果Free没有被实现,那么析构函数将不得不如下所示:

 if obj1 <> nil then obj1.Free; if obj2 <> nil then obj2.Free; if obj3 <> nil then obj3.Free; .... 

下一块拼图是Delphi构造函数将实例内存初始化为零 。 这意味着任何未分配的对象引用字段都是nil

把这一切放在一起,析构函数代码现在成为

 obj1.Free; obj2.Free; obj3.Free; .... 

你应该select后者,因为它更可读。

有一种情况需要testing引用是否在析构函数中分配。 如果你需要在破坏对象之前调用对象的任何方法,那么显然你必须警惕它的可能性nil 。 所以如果这个代码出现在析构函数中,它将会冒着AV的风险:

 FSettings.Save; FSettings.Free; 

相反,你写

 if Assigned(FSettings) then begin FSettings.Save; FSettings.Free; end; 

testing在析构函数之外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; 

在这种情况下,再次不需要在TMyObject.UpdatetestingAssigned 。 原因是你根本不能调用TMyObject.Update除非TMyObject.Update的构造TMyObject成功。 如果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; 

在这种情况下,课程支持两种操作模式,无论是否logging。 这个决定是在施工时间进行的,任何引用测井对象的方法都必须testing它的存在。

这种不常见的代码forms使得更加重要的是,您不会将非法的调用用于非可选对象的Assigned 。 当你看到代码中的if Assigned(FLogger)应该是一个清楚的指示,表明这个类可以在FLogger不存在的情况下正常运行。 如果你在你的代码周围喷涂了免费的电话,那么你就无法一眼就看出一个对象是否应该永远存在。

Free有一些特殊的逻辑:它检查Self是否nil ,如果是,它将返回而不做任何事情 – 所以即使X nil ,也可以安全地调用X.Free 。 当你编写析构函数时这很重要 – 大卫在他的答案中有更多细节。

你可以看看Free的源代码,看看它是如何工作的。 我没有Delphi源代码,但它是这样的:

 procedure TObject.Free; begin if Self <> nil then Destroy; end; 

或者,如果您愿意,可以将其视为使用Assigned的等效代码:

 procedure TObject.Free; begin if Assigned(Self) then Destroy; end; 

你可以编写你自己的方法来检查if Self <> nil ,只要它们是静态的(即不是virtualdynamic )实例方法 (感谢David Heffernan提供的文档链接)。 但在Delphi库中, Free是我知道的唯一使用这个技巧的方法。

因此,在调用Free之前,您不需要检查variables是否已Assigned ; 它已经为你做了。 这就是为什么这个build议是调用Free而不是直接调用Destroy :如果你在一个nil引用上调用Destroy,你会得到一个访问冲突。

为什么你不应该打电话

 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; 

我并不完全确定,但似乎:

 if assigned(object.owner) then object.free 

工作正常。 在这个例子中是这样的

 if assigned(FBitmap.owner) then FBitmap.free