为什么在访问对象之前我不应该使用“if Assigned()”?

时间:2011-12-17 23:57:24

标签: delphi

这个问题是关于stackoverflow的人们特别评论的延续,我现在已经看过几次不同的时间了。我和教我Delphi的开发人员一样,为了保证安全,在释放对象之前,以及在做其他各种事情之前总是先检查if assigned()。但是,我现在告诉我应该添加此检查。我想知道如果我这样做,应用程序编译/运行的方式是否存在任何差异,或者它是否会对结果产生影响......

if assigned(SomeObject) then SomeObject.Free;

我们说我有一个表单,并且我会在表单创建时在后台创建一个位图对象,并在我完成它时释放它。现在我想我的问题是当我试图访问可能在某些时候可能免费的对象时,我已经习惯于对我的很多代码进行检查。即使没有必要,我也一直在使用它。我想要彻底......

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的新自定义列表对象。对于此列表中的每个项目,我当然必须创建/释放每个项目对象。有几种不同的创建项目的方法,以及一些不同的方法来销毁项目(添加/删除是最常见的)。我确信在这里提供这种保护是一种非常好的做法......

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;

在许多场景中,至少我希望在我尝试释放它之前仍然创建该对象。但是你永远不知道在未来物体获得自由之前会发生什么样的滑倒。我总是使用这张支票,但现在我被告知我不应该这样做,而且我仍然不明白为什么。


修改

以下是一个尝试向您解释为什么我有这样做习惯的例子:

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 <> nilif Assigned(SomeCreatedObject)不同,因为在释放SomeCreatedObject后,它不会评估为nil。所以这两项检查都是必要的。

4 个答案:

答案 0 :(得分:123)

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

Assigned函数的含义

您问题中的大部分代码都背叛了对Assigned函数的错误理解。 documentation说明了这一点:

  

测试 nil (未分配)指针或程序变量。

     

使用已分配来确定指针或过程   P引用的是 nil 。 P必须是指针的变量引用或   程序类型。

     

已分配(P)对应于测试 P&lt;&gt; nil 表示指针变量   和 @P&lt;&gt;没有的程序变量。

     

已分配如果P nil 则返回 False ,否则返回 True

     

提示:在测试对象事件和分配程序时,您   无法测试 nil ,并且使用已分配是正确的方式。

     

...

     

注意已分配无法检测到悬空指针 - 也就是说, nil ,但不再指向有效指针数据

Assigned的含义因指针和程序变量而异。在本答案的其余部分中,我们将仅考虑指针变量,因为这是问题的上下文。请注意,对象引用实现为指针变量。

从文档中得到的关键点是,对于指针变量:

  1. Assigned相当于测试<> nil
  2. Assigned无法检测指针或对象引用是否有效。
  3. 在这个问题的背景下,这意味着

    if obj<>nil
    

    if Assigned(obj)
    

    完全可以互换。

    在致电Assigned

    之前测试Free

    TObject.Free的实施非常特殊。

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

    这允许您在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;
    ....
    

    拼图的下一部分是Delphi constructors initialise the instance memory to zero。这意味着任何未分配的对象引用字段都是nil

    将所有这些放在一起,析构函数代码现在变为

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

    您应该选择后一个选项,因为它更具可读性。

    有一种情况需要测试是否在析构函数中分配了引用。如果你需要在破坏它之前调用该对象的任何方法,那么显然你必须防止它被nil的可能性。因此,如果它出现在析构函数中,此代码将冒AV的风险:

    FSettings.Save;
    FSettings.Free;
    

    相反,你写

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

    在析构函数外部测试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;
    

    在这种情况下,再次无需在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;
    

    在这种情况下,该类支持两种操作模式,包括和不包含日志记录。决定是在构造时进行的,任何引用日志对象的方法都必须测试它的存在。

    这种不常见的代码形式使得对Assigned的非可选对象使用虚假调用变得更加重要。当您在代码中看到if Assigned(FLogger)时,该代码应该清楚地表明该类可以在FLogger不存在的情况下正常运行。如果你在代码周围向Assigned发出无偿呼叫,那么你就无法一眼就知道某个对象是否应该始终存在。

答案 1 :(得分:21)

Free有一些特殊逻辑:它会检查Self是否为nil,如果是,则返回时不做任何操作 - 因此您可以安全地调用{{1} }即使X.FreeX。当您编写析构函数时,这很重要--David在his answer中有更多详细信息。

您可以查看nil的源代码,了解其工作原理。我不熟悉Delphi源代码,但它是这样的:

Free

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

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

您可以编写自己的方法来检查procedure TObject.Free; begin if Assigned(Self) then Destroy; end; ,只要他们重新static (i.e., not virtual or dynamic) instance methods(感谢David Heffernan的文档链接)。但是在Delphi库中,if Self <> nil是我所知道的唯一使用此技巧的方法。

因此,在致电Free之前,您无需检查变量是否为Assigned;它已经为你做到了。这就是为什么建议调用Free而不是直接调用Free的原因:如果您在Destroy引用上调用了Destroy,则会出现访问冲突。

答案 2 :(得分:16)

为什么你不应该打电话

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;

答案 3 :(得分:-3)

我不完全确定,但似乎:

if assigned(object.owner) then object.free 

工作正常。在这个例子中它将是

if assigned(FBitmap.owner) then FBitmap.free
相关问题