为什么我不应该使用“if Assigned()”在使用或释放​​东西之前? [英] Why should I not use "if Assigned()" before using or freeing things?

查看:274
本文介绍了为什么我不应该使用“if Assigned()”在使用或释放​​东西之前?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题是一个来自stackoverflow的人的特殊评论的继续,我现在看到了几个不同的时代。我和开发人员一起教我Delphi,为了保护事物的安全,在释放对象之前,一直在检查,如果被赋值(),然后再做其他各种事情。不过,我现在被告知,我不应该加入这个支票。我想知道如果我这样做,应用程序如何编译/运行有任何差异,或者如果它不会影响结果...

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 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;

我的观点是,如果SomeCreatedObject<>因为在释放 SomeCreatedObject 之后,nil 与分配(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:


测试一个零(未分配)指针或过程变量。

Tests for a nil (unassigned) pointer or procedural variable.

使用
分配以确定指针或过程由P
引用为零。 P必须是指针或程序
类型的变量引用。分配(P)对应于指针
变量的测试P< nil,对于程序变量,对应于@P

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.

分配的返回
如果P为n,则为False。否则为True。

Assigned returns False if P is nil, True otherwise.

注意:指定无法检测到一个
悬挂指针 - 也就是说,一个不为零但不再指向
有效数据的指针。例如,在Assigned的代码示例中,Assigned
没有检测到P无效。

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.

关键点从这里获取的是:

The key points to take from this are:


  1. 已分配相当于测试

  2. 已分配无法检测指针或对象引用是否有效。

  1. Assigned is equivalent to testing <> nil.
  2. Assigned cannot detect whether the pointer or object reference is valid or not.

在这个问题的上下文中,这意味着

What this means in the context of this question is that

if obj<>nil

if Assigned(obj)

完全可以互换。

测试在调用之前分配免费

执行 TObject.Free 非常特别。

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.

您希望允许 Free nil 对象引用中被调用源于构造函数和析构函数在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;

测试在析构函数之外分配的

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;

在这种情况下,再次无需测试 Assigned TMyObject.Update 。原因是你不能调用 TMyObject.Update ,除非 TMyObject 的构造函数成功。而如果 TMyObject 的构造函数成功,那么您可以确定已分配了 FSettings 。所以再一次,通过将分配给 c $ c>

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.

这种不常见的代码形式使您更加重要不要对非可选对象使用虚假调用分配的。当您在代码中看到如果分配(FLogger)应该是一个明确的指示,您可以使用 FLogger 不存在如果您在您的代码周围发送免费电话 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆