为什么我不应该使用“ if Assigned()”?在访问对象之前? [英] Why should I not use "if Assigned()" before accessing objects?

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

问题描述

这个问题是人们对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:


  1. 已分配等同于测试<> nil

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

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

在此问题中这意味着

if obj<>nil

if Assigned(obj)

完全可互换。

在调用 Free 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;

在这种情况下,无需测试已分配 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 不存在。如果在代码周围对 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天全站免登陆