Assigned 的迷思

作者:高张远瞩(HiLoveS)

博客:http://www.cnblogs.com/hiloves/

转载请保留该信息

在初学Delphi时,有一个疑问:如何判断对象已创建。在论坛上问了不少人,回答有“用Assigned”和“没法判断”两种。我开始在代码中用Assigned,却发现这个函数好像不起作用,于是偏向于另一个答案,认为“没法判断”对象是否创建了。通过一段时间的深入学习与思考,感悟到原来两种答案都对。如果你也有此类疑问就随我看下去。

疑问1:是否可以判断对象已创建?

Delphi中的对象是堆分配,分配方式类似链表,系统会分配空闲的内存给对象,因为这一机制,对象的内存地址是不固定的,并可能是不连续的内存块,并且一旦该对象被销毁,系统会收回它所占用的内存块,并标记为空闲内存,如果有需要,系统会将该片内存分配给别的对象使用。如果仅凭对象标识符指向的那片内存中是否有数据来判断该对象是否已创建,显然是不可行的。因为指向的内存区的数据可能是该对象的(对象创建未销毁),也可能是别的对象的(该片内存被系统重新分配给了别的对象)。

因此,从这点上讲你是没办法判断对象是否已创建。但为什么还是有人认为并给出了另一个答案呢?

疑问2:为什么有人说Assigned可以用来判断对象是否已创建?

没法从内存数据上判断,但还有曲线救国的办法。就是判断对象标识符是否指向某个内存地址。test: TObject,test不是对象的本体,它只是对象标识符,其本质是个指针。这个方法就是,如果这个指针指向一个内存地址,则这个对象就创建了。而且事实确实如此,test := TObject.Create,这一句确实赋给了test正确的对象内存地址。因此,如果Assigned(test)返回了True,则认为test对象已创建了。

我欣喜若狂开始在代码中用上Assigned后,却被浇了一盆冷水,Assigned不管用。这是怎么回事?

疑问3:为什么Assigned不起作用?

先来看一段代码。

Code:
interface
uses
  Dialogs;
type
  TTest = class(TObject)
  public
    procedure Show;
    constructor Create;
    destructor Destroy; override;
  end;

implementation

procedure TTest.Show;
begin
  ShowMessage('Object Show');
end;

constructor TTest.Create;
begin
  ShowMessage('Object Create');
end;

destructor TTest.Destroy;
begin
  ShowMessage('Object Destroy');
end;

procedure Test1;
var
  TestObj1, TestDestroy: TTest;
begin
  if Assigned(TestObj1) then
  begin
    ShowMessage('Assigned True');  //执行了
  end;

  TestDestroy := TTest.Create;
  TestDestroy.Destroy;
  if Assigned(TestDestroy) then
  begin
    ShowMessage('Assigned True');  //执行了
  end;
end;

按照我们的期待,Test1中的两个ShowMessage是不应该执行的。原因就是TestObj1还没被创建呢,Assigned(TestObj1)应该返回False才对,TestDestroy被销毁了,Assigned(TestDestroy)应该返回False才对。但不幸的是,两个ShowMessage都运行了。Assigned彻底不起作用。

这是怎么回事?原因很简单,我们一厢情愿的认为:当声明一个对象时其指针是指向nil的,当销毁一个对象后其指针自动变为nil。呵呵,Delphi可从来没说过它会这么干。

事实是这样的,声明一个全局对象时Delphi会自动将其指向nil,声明一个局部对象时Delphi会将其指向一个随机的地址而不是nil,当销毁一个对象时Delphi不会自动对对象标识符做什么,原来指哪里销毁后还是指哪里。

看看全局对象会怎么样。

Code:
var
  TestObj2: TTest;  //全局对象

implementation

procedure Test2;
begin
  if Assigned(TestObj2) then
  begin
    ShowMessage('Assigned True');  //没有执行
  end;
end;

TestObj2是全局对象,Test2中的ShowMessage没有执行。

如何才能让Assigned恢复本来的雄风呢?

疑问4:Assigned不起作用,怎么办?

原因找到就很好解决。

Code:
procedure Test4;
var
  TestObj4: TTest;
begin
  TestObj4 := nil;  //声明之后,创建之前马上设为nil
  if Assigned(TestObj4) then
  begin
    ShowMessage('Assigned True');  //不会执行
  end;

  TestObj4 := TTest.Create;
  if Assigned(TestObj4) then
  begin
    ShowMessage('Assigned True');  //执行
    TestObj4.Destroy;
    TestObj4 := nil;  //销毁后马上设为nil
  end;

  if Assigned(TestObj4) then
  begin
    ShowMessage('Assigned True');  //不会执行
  end;
end;

针对对象的不同生命期,在声明之后、创建之前和销毁后马上设为nil,让Assigned恢复雄风。

还有人说可以用“= nil”来判断,用哪个好呢?

疑问5:“Assigned” PK “= nil”,哪个好?

说来只是个效率问题,你在System.pas中找不到有关Assigned的代码,它是Delphi Intrinsic Routines,Delphi内部函数,是由编译器实现的,因此效率比用“= nil”高。

另:有关Delphi Intrinsic Routines的详情请看,这些函数效率较高

http://docwiki.embarcadero.com/RADStudio/en/Delphi_Intrinsic_Routines

疑问6:穿越了的对象参数在另一个地方被销毁会发生什么情况?

有时候,我们会将一个对象当做参数传递到另一个对象中或函数中,供它们调用。如果,在创建该对象的函数中把它销毁掉,那么穿越到别的对象中的这个对象参数会怎么样?

Code:
type
  TTest2 = class(TObject)
  public
    fObj: TTest;
    procedure SetObj(fObj: TTest);
    procedure Show;
  end;

implementation

procedure TTest2.SetObj(fObj: TTest);
begin
  Self.fObj := fObj;
end;

procedure TTest2.Show;
begin
  if Assigned(fObj) then
  begin
    ShowMessage('fObj True');  //执行了
    fObj.Destroy;  //重复销毁,出错
  end;
end;

procedure Test9;
var
  TestObj9: TTest;
  Test2Obj: TTest2;
begin
  TestObj9 := TTest.Create;
  Test2Obj := TTest2.Create;

  Test2Obj.SetObj(TestObj9);

  TestObj9.Destroy;
  TestObj9 := nil;

  Test2Obj.Show;  //出错

  Test2Obj.Destroy;
end;

在这种“传引用”的方式中,销毁TestObj9后,Test2Obj中的fObj还是指向销毁前的内存地址,所以Show的代码出错了。如果使用Assigned,确实对这种情况无能为力,毕竟它的依据是对象标识符是否指向某个内存地址,如果TestObj9在外部被销毁,它就会判断失误。除非使用传值或复制对象的方式,我还没想到有没有方法解决这一问题。

上述测试代码下载地址:https://files.cnblogs.com/hiloves/AssignedFreeCode.rar

原文地址:https://www.cnblogs.com/hiloves/p/1719775.html