为什么我不应该使用“ if Assigned()”?在访问对象之前? [英] Why should I not use "if Assigned()" before accessing objects?
问题描述
这个问题是人们对stackoverflow的特定评论的延续,我现在已经看到了几次不同的评论。我和教我Delphi的开发人员一起,为了确保事情安全,总是在释放对象之前以及执行其他各种操作之前先检查 ifsigned()
。但是,现在我被告知,我应该不添加此检查。我想知道如果执行此操作,则应用程序在编译/运行方式方面是否存在差异,或者它根本不会影响结果...
This question is a continuance of a particular comment from people on stackoverflow which I've seen a few different times now. I, along with the developer who taught me Delp in order to keep things safe, have always put a check if assigned()
before freeing objects, and before doing other various things. However, I'm now told that I should not be adding this check. I'd like to know if there is any difference in how the application compiles/runs if I do this, or if it won't affect the result at all...
if assigned(SomeObject) then SomeObject.Free;
假设我有一个表单,并且我正在表单的背景中创建一个位图对象创建,并在完成后将其释放。现在,我想我的问题是,当我尝试访问某些时候可能已释放的对象时,我已经习惯了对很多代码进行检查。即使没有必要,我也一直在使用它。我想更彻底...
Let's say I have a form, and I'm creating a bitmap object in the background upon the form's creation, and freeing it when I'm done with it. Now I guess my problem is I got too used to putting this check on a lot of my code when I'm trying to access objects which might potentially have been free'd at some point. I've been using it even when it's not necessary. I like to be thorough...
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 <的新自定义列表对象/ code>的
TMyListItem
。对于此列表中的每个项目,我当然必须创建/释放每个项目对象。创建项目的方式有几种,销毁项目的方式也有几种(最常见的是添加/删除)。我确定将保护设置在这里是一种很好的做法...
Now let's say I'm introducing a new custom list object called TMyList
of TMyListItem
. For each item in this list, of course I have to create/free each item object. There's a few different ways of creating an item, as well as a few different ways of destroying an item (Add/Delete being the most common). I'm sure it's a very good practice to put this protection here...
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;
在许多情况下,至少我希望在尝试释放对象之前仍然创建该对象。但是,您永远都不知道将来在对象应获得的免费释放之前会发生什么滑动。我一直使用这张支票,但是现在我被告知我不应该这样做,而且我仍然不明白为什么。
In many scenarios, at least I would hope that the object is still created before I try to free it. But you never know what slips might happen in the future where an object gets free'd before it's supposed to. I've always used this check, but now I'm being told I shouldn't, and I still don't understand why.
编辑
下面是一个示例,试图向您解释为什么我有这样做的习惯:
Here's an example to try to explain to you why I have a habit of doing this:
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
与如果Assigned(SomeCreatedObject)
不同,因为释放 SomeCreatedObject
后,它会的结果不为 nil
。因此,两个检查都应该是必要的。
My point is that if SomeCreatedObject <> nil
is not the same as if Assigned(SomeCreatedObject)
because after freeing SomeCreatedObject
, it does not evaluate to nil
. So both checks should be necessary.
推荐答案
这是一个非常广泛的问题,有很多不同的角度。
This is a very broad question with many different angles.
已分配
函数的含义
The meaning of the Assigned
function
很多您问题中的代码显示出对 Assigned
函数的错误理解。 文档指出:
Much of the code in your question betrays an incorrect understanding of the Assigned
function. The documentation states this:
测试 nil (未分配)指针或过程变量。
Tests for a nil (unassigned) pointer or procedural variable.
使用已分配来确定P引用的指针或过程
是否为 nil 。 P必须是指针或
过程类型的变量引用。
Use Assigned to determine whether the pointer or the procedure referenced by P is nil. P must be a variable reference of a pointer or procedural type.
已分配(P)对应于测试 P<> nil 表示指针变量,
和 @P<> nil 表示过程变量。
Assigned(P) corresponds to the test P <> nil for a pointer variable, and @P <> nil for a procedural variable.
已分配,如果P为 nil ,则返回 False ,否则为 True 。
Assigned returns False if P is nil, True otherwise.
提示:测试对象事件和分配过程时,您
无法测试 nil ,也无法使用 Assigned
Tip: When testing object events and procedures for assignment, you cannot test for nil, and using Assigned is the right way.
....
注意:已分配无法检测到悬空指针,即不是 nil 但不再指向有效数据的指针。
Note: Assigned cannot detect a dangling pointer--that is, one that is not nil, but that no longer points to valid data.
对于指针变量和过程变量,已分配
的含义不同。在此答案的其余部分中,我们将仅考虑指针变量,因为这是问题的上下文。请注意,对象引用是作为指针变量实现的。
The meaning of Assigned
differs for pointer and procedural variables. In the rest of this answer we will consider pointer variables only, since that is the context of the question. Note that an object reference is implemented as a pointer variable.
文档中的关键点是指针变量:
The key points to take from the documentation are that, for pointer variables:
-
已分配
等同于测试<> nil
。 -
已分配
无法检测指针或对象引用是否有效。
Assigned
is equivalent to testing<> nil
.Assigned
cannot detect whether the pointer or object reference is valid or not.
在此问题中这意味着
if obj<>nil
和
if Assigned(obj)
完全可互换。
在调用 Free $ c $之前测试
Assigned
c>
Testing Assigned
before calling Free
TObject.Free
非常特殊。
The implementation of TObject.Free
is very special.
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;
这允许您在计算机上调用免费
nil
的对象引用无效。对于它的价值,我知道在RTL / VCL中没有其他使用这种技巧的地方。
This allows you to call Free
on an object reference that is nil
and doing so has no effect. For what it is worth, I am aware of no other place in the RTL/VCL where such a trick is used.
为什么要允许<$在 nil
对象引用上调用的c $ c> Free 源自于Delphi中构造函数和析构函数的操作方式。
The reason why you would want to allow Free
to be called on a nil
object reference stems from the way constructors and destructors operate in Delphi.
在构造函数中引发异常时,将调用析构函数。这样做是为了取消分配在成功的构造函数那部分中分配的所有资源。如果未实现 Free
,则析构函数将看起来像这样:
When an exception is raised in a constructor, the destructor is called. This is done in order to deallocate any resources that were allocated in that part of the constructor that succeeded. If Free
was not implemented as it is then destructors would have to look like this:
if obj1 <> nil then
obj1.Free;
if obj2 <> nil then
obj2.Free;
if obj3 <> nil then
obj3.Free;
....
拼图的下一个片段是 Delphi构造函数会将实例内存初始化为零。这意味着任何未分配的对象引用字段均为 nil
。
The next piece of the jigsaw is that Delphi constructors initialise the instance memory to zero. This means that any unassigned object reference fields are nil
.
将所有内容放在一起,析构函数代码现在变为
Put this all together and the destructor code now becomes
obj1.Free;
obj2.Free;
obj3.Free;
....
您应该选择后者,因为它更具可读性。
You should choose the latter option because it is much more readable.
在一种情况下,您需要测试引用是否在析构函数中分配。如果需要在销毁对象之前调用该对象的任何方法,那么显然您必须防止其为 nil
的可能性。因此,如果此代码出现在析构函数中,则会冒AV的风险:
There is one scenario where you need to test if the reference is assigned in a destructor. If you need to call any method on the object before destroying it then clearly you must guard against the possibility of it being nil
. So this code would run the risk of an AV if it appeared in a destructor:
FSettings.Save;
FSettings.Free;
相反,您写
if Assigned(FSettings) then
begin
FSettings.Save;
FSettings.Free;
end;
在析构函数外部测试 Assigned
Testing Assigned
outside a destructor
您还谈到了在析构函数之外编写防御性代码。例如:
You also talk about writing defensive code outside a destructor. For example:
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;
在这种情况下,无需测试已分配$ c
TMyObject.Update
中的$ c>。原因是您无法调用 TMyObject.Update
,除非 TMyObject
的构造函数成功。而且,如果 TMyObject
的构造函数成功,那么您肯定会知道已分配 FSettings
。因此,再次通过对 Assigned
进行虚假调用,使代码的可读性和维护难度大大降低。
In this situation there is again no need to test Assigned
in TMyObject.Update
. The reason being that you simply cannot call TMyObject.Update
unless the constructor of TMyObject
succeeded. And if the constructor of TMyObject
succeeded then you know for sure that FSettings
was assigned. So again you make your code much less readable and harder to maintain by putting in spurious calls to Assigned
.
是一种情况,您需要编写如果已分配
,并且该对象的存在是可选的。例如,
There is a scenario where you need to write if Assigned
and that is where the existence of the object in question is optional. For example
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;
在这种情况下,该类支持两种操作模式,带和不带日志。决定是在构造时做出的,引用日志记录对象的任何方法都必须测试其存在。
In this scenario the class supports two modes of operation, with and without logging. The decision is taken at construction time and any methods which refer to the logging object must test for its existence.
这种不常见的代码形式使您更加重要不要对非可选对象使用对 Assigned
的虚假调用。当您在代码中看到 if Assigned(FLogger)
时,应该清楚地表明该类可以通过 FLogger $ c $正常运行c>不存在。如果在代码周围对
Assigned
进行了不必要的调用,那么您一眼便无法分辨对象是否应该一直存在。
This not uncommon form of code makes it even more important that you don't use spurious calls to Assigned
for non-optional objects. When you see if Assigned(FLogger)
in code that should be a clear indication to you that the class can operate normally with FLogger
not in existence. If you spray gratuitous calls to Assigned
around your code then you lose the ability to tell at a glance whether or not an object should always exist.
这篇关于为什么我不应该使用“ if Assigned()”?在访问对象之前?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!