这个问题是stackoverflow上人们特别评论的延续,我现在已经看过几次不同的时间了.我和教我Delphi的开发人员一样,为了保证安全,if assigned()在释放对象之前,以及在做其他各种事情之前总是先做检查.但是,我现在被告知我不应该添加此支票.我想知道如果我这样做,应用程序编译/运行的方式是否存在任何差异,或者它是否会对结果产生影响...
if assigned(SomeObject) then SomeObject.Free;
Run Code Online (Sandbox Code Playgroud)
假设我有一个表单,我在表单创建时在后台创建一个位图对象,并在完成后释放它.现在我想我的问题是,当我试图访问可能在某些时候可能已经免费的对象时,我已经习惯了对我的很多代码进行检查.即使没有必要,我也一直在使用它.我喜欢彻底......
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.
Run Code Online (Sandbox Code Playgroud)
现在让我们说我正在引入一个名为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;
Run Code Online (Sandbox Code Playgroud)
在许多情况下,至少我希望在我尝试释放它之前仍然创建对象.但是你永远不知道未来在对象被释放之前会发生什么样的滑动.我总是使用这张支票,但现在我被告知我不应该这样,而且我仍然不明白为什么.
编辑
这是一个例子,试图向你解释为什么我有这样做的习惯:
procedure TForm1.FormDestroy(Sender: TObject);
begin
SomeCreatedObject.Free;
if SomeCreatedObject = nil then
ShowMessage('Object is nil')
else
ShowMessage('Object is not nil');
end;
Run Code Online (Sandbox Code Playgroud)
我的观点是if SomeCreatedObject <> nil不一样的if Assigned(SomeCreatedObject),因为释放后SomeCreatedObject,其计算结果不是nil.所以这两项检查都是必要的.
Dav*_*nan 130
这是一个涉及许多不同角度的非常广泛的问题.
Assigned功能的含义
你问题中的大部分代码都背叛了对Assigned函数的错误理解.该文档的状态如下:
测试nil(未分配)指针或过程变量.
使用Assigned确定P引用的指针或过程是否为nil.P必须是指针或过程类型的变量引用.
Assigned(P)对应于指针变量的测试P <> nil,对于程序变量对应于@P <> nil.
如果P 为零,则赋值返回False,否则为True.
提示:在测试对象事件和分配过程时,无法测试nil,并且使用Assigned是正确的方法.
....
注意:Assigned无法检测到悬空指针 - 也就是说,指针不是nil,但不再指向有效数据.
Assigned指针和程序变量的含义不同.在本答案的其余部分中,我们将仅考虑指针变量,因为这是问题的上下文.请注意,对象引用实现为指针变量.
从文档中得到的关键点是,对于指针变量:
Assigned相当于测试<> nil.Assigned 无法检测指针或对象引用是否有效.在这个问题的背景下,这意味着什么
if obj<>nil
Run Code Online (Sandbox Code Playgroud)
和
if Assigned(obj)
Run Code Online (Sandbox Code Playgroud)
是完全可以互换的.
Assigned呼叫前测试Free
执行TObject.Free非常特殊.
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;
Run Code Online (Sandbox Code Playgroud)
这允许您调用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;
....
Run Code Online (Sandbox Code Playgroud)
拼图的下一部分是Delphi构造函数将实例内存初始化为零.这意味着任何未分配的对象引用字段都是nil.
把这一切放在一起,析构函数代码现在变成了
obj1.Free;
obj2.Free;
obj3.Free;
....
Run Code Online (Sandbox Code Playgroud)
您应该选择后一个选项,因为它更具可读性.
有一种情况需要测试是否在析构函数中分配了引用.如果你需要在破坏它之前调用该对象的任何方法,那么显然你必须防止它存在的可能性nil.因此,如果它出现在析构函数中,此代码将冒AV的风险:
FSettings.Save;
FSettings.Free;
Run Code Online (Sandbox Code Playgroud)
相反,你写
if Assigned(FSettings) then
begin
FSettings.Save;
FSettings.Free;
end;
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)
在这种情况下再没有需要测试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;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,该类支持两种操作模式,包括和不使用日志记录.决定是在构造时进行的,任何引用日志记录对象的方法都必须测试它的存在.
这种不常见的代码形式使得Assigned对非可选对象不使用伪调用更为重要.当您if Assigned(FLogger)在代码中看到应该清楚地表明该类可以在FLogger不存在的情况下正常运行.如果你Assigned在你的代码周围喷射无偿的电话,那么你就无法一眼就知道对象是否应该永远存在.
Joe*_*ite 22
Free有一些特殊的逻辑:它检查是否Self是nil,如果是这样,它返回而不做任何事情-这样你就可以安全地调用X.Free,即使X是nil.当你编写析构函数时,这很重要 - 大卫在他的答案中有更多的细节.
您可以查看源代码Free以了解其工作原理.我没有方便的Delphi源代码,但它是这样的:
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;
Run Code Online (Sandbox Code Playgroud)
或者,如果您愿意,可以将其视为等效代码,使用Assigned:
procedure TObject.Free;
begin
if Assigned(Self) then
Destroy;
end;
Run Code Online (Sandbox Code Playgroud)
您可以编写自己的方法来检查if Self <> nil,只要它们是静态(即,不是virtual或dynamic)实例方法(感谢David Heffernan的文档链接).但是在Delphi库中,Free我所知道的唯一方法是使用这个技巧.
因此,Assigned在调用之前,您无需检查变量是否存在Free; 它已经为你做到了.这就是为什么建议是调用Free而不是Destroy直接调用的原因:如果你在nil引用上调用了Destroy ,你就会遇到访问冲突.
Mar*_*ner 17
为什么你不应该打电话
if Assigned(SomeObject) then
SomeObject.Free;
Run Code Online (Sandbox Code Playgroud)
只是因为你会执行这样的事情
if Assigned(SomeObject) then
if Assigned(SomeObject) then
SomeObject.Destroy;
Run Code Online (Sandbox Code Playgroud)
如果你刚才打电话就是SomeObject.Free;这样
if Assigned(SomeObject) then
SomeObject.Destroy;
Run Code Online (Sandbox Code Playgroud)
对于您的更新,如果您害怕对象实例引用,请使用FreeAndNil.它会破坏和取消引用你的对象
FreeAndNil(SomeObject);
Run Code Online (Sandbox Code Playgroud)
它就像你打电话一样
SomeObject.Free;
SomeObject := nil;
Run Code Online (Sandbox Code Playgroud)