delphi – 为什么我不应该使用“如果Assigned()”之前使用或释放​​的东西?

前端之家收集整理的这篇文章主要介绍了delphi – 为什么我不应该使用“如果Assigned()”之前使用或释放​​的东西?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
这个问题是人们对stackoverflow的一个特定评论的延续,我已经看到了几个不同的时间现在。我,以及开发人员教我德尔福,为了保持安全,总是放一个检查如果assign()释放对象之前,在做其他各种事情之前。但是,我现在被告知,我不应该添加这个检查。我想知道如果应用程序编译/运行如果我这样做,或者如果它不会影响任何结果的任何差异…
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.

现在让我们说,我引入一个新的自定义列表对象TMyListItem TMyList。对于这个列表中的每个项目,当然我必须创建/释放每个项目对象。有几种不同的方式创建一个项目,以及一些不同的方式来销毁一个项目(添加/删除是最常见的)。我相信这是一个很好的做法,把这种保护在这里…

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;

我的观点是如果SomeCreatedObject<> nil与Assigned(SomeCreatedObject)不同,因为释放SomeCreatedObject后,它不会计算为nil。所以两个检查都应该是必要的。

解决方法

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

Assigned函数的含义

你的问题中的大部分代码错误地理解了Assigned函数documentation说:

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

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.

Assigned returns
False if P is nil,True otherwise.

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.

从这里的关键点是:

> Assigned等价于测试<>零。
> Assigned无法检测指针或对象引用是否有效。

在这个问题的上下文中这是什么意思

if obj<>nil

if Assigned(obj)

是完全可互换的。

测试在调用Free之前分配

TObject.Free的实现非常特殊。

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

这允许你在一个为nil的对象引用上调用Free,这样做没有任何效果。对于什么是值得的,我知道在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.这意味着任何未分配的对象引用字段为零。

把这一切放在一起,析构函数代码现在变成了

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

你应该选择后一个选项,因为它更容易阅读。

有一种情况,您需要测试参考是否在析构函数中分配。如果你需要调用对象上的任何方法,在销毁它之前,然后清楚你必须防止它的可能性为零。所以这个代码将运行AV的风险,如果它出现在析构函数中:

FSettings.Save;
FSettings.Free;

相反你写

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

测试在析构函数外部分配

你还谈谈在析构函数之外编写防御性代码。例如:

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中测试Assigned。原因是你根本不能调用TMyObject.Update,除非TMyObject的构造函数成功。如果TMyObject的构造函数成功,那么你肯定知道分配了FSettings。所以再次,你让你的代码更少的可读性和更难维护通过对Assigned的虚假调用

有一种情况,你需要写如果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的伪调用。当你看到代码中的Assigned(FLogger)时,应该清楚地表明类可以正常运行,而FLogger不存在。如果你对你的代码进行免费的调用,那么你就失去了一个对象总是存在的能力。

猜你在找的Delphi相关文章