用户登录  |  用户注册
首 页商业源码原创产品编程论坛
当前位置:PB创新网文章中心编程技巧Delphi

Delphi中智能对象的实现---by熊恒(beta)

减小字体 增大字体 作者:佚名  来源:本站整理  发布时间:2009-03-16 20:19:34
声明:本文乃 熊恒(beta) 原创,如要转载请保持文章完整。

                Delphi 中智能对象的实现 --- by 熊恒(beta)

回想一下,我们中的大部分都应该写过类似这样的代码吧:

  procedure TYourClass.SomeMethod(SomeParam: SomeType);
  var
    SomeObj: TSomeClass;
  begin
    SomeObj := TSomeClass.Create;
    try
      SomeObj.DoSomeThing(SomeParam);
    finally
      SomeObj.Free;
    end;
  end;

在一个方法(或过程、函数)中,你需要临时创建一个对象,在离开方法前,需要将其
释放掉。于是,我们一次又一次不厌其烦的写着这样的 try-finally-end 的代码。这时
我不由地羡慕起 C++ 程序员来,他们可以吧对象分配在栈上,离开作用域后,对象就会
自动释放,多方便啊:)

可惜,Delphi 中的对象都是分配在堆上的。要是 Delphi 中的对象也可以向 C++ 那样,
在方法中需要时临时创建,使用完了就不用管,离开方法时自动会被释放(无论是否出现
异常,这也很重要),那该多好啊。

但是,Delphi 没有直接提供这样的机制。不过我们还是有办法:)考虑一下,C++ 中的
智能指针是怎么回事?一个指针分配空间后,也像局部对象一样不管了,离开作用域后会
自动释放其分配的空间。实际上就是用了一个局部对象包裹之,在离开作用域之后,局部
对象自动释放,在其析构函数中释放那指针的空间。

那么,在 Delphi 中是否有类似的机制呢?其实是有的,Delphi 中的接口(Interface),
在离开(过程级的)作用域后,会自动释放(其实现对象),我们可在这上面做点文章。
在这里,就可以借用 C++ 中智能指针的思想,用可以自动释放的局部对象以释放特定的
指针;用可以自动释放的接口(的实现对象)以释放特定的对象。只要我们设法把一个需
要自动释放的对象的引用传到一个接口中,那么我们就可以在接口释放时,释放该对象。
关键就在于这个接口和需要自动释放的对象的生命周期应该一致。因为我们要自动释放的
对象是局部变量,这个接口也应该是一个局部变量。

在明确这一点后,实现就相对比较容易了,去掉空行和注释一共不到 40 行,呵呵。那么
先来个例子,看看这个东西是怎么用的,然后给出实现。

  program SafeObjTest;

  {$APPTYPE CONSOLE}

  uses
    SysUtils, XhSafeObj;

  type
    TTest = class
    private
      FName: string;
    public
      constructor Create(const Name: string);
      destructor Destroy; override;
      procedure SayHello;
    end;

  { TTest }

  constructor TTest.Create(const Name: string);
  begin
    FName := Name;
  end;

  destructor TTest.Destroy;
  begin
    Writeln(FName, '' is gone'');

    inherited;
  end;

  procedure TTest.SayHello;
  begin
    Writeln(''Hello, I am '', FName);
  end;

  procedure Proc1;
  var
    Test: TTest;
  begin
    Test := TTest.Create(''Tom'');
    SafeObject(Test);
    Test.SayHello;
  end;

  procedure Proc2;
  var
    Test: TTest;
  begin
    Test := TTest.Create(''Jim'');
    SafeObject(Test);
    Test.SayHello;
    raise Exception.Create(''Something wrong'');
    Test.SayHello;
  end;

  procedure Proc3;
  var
    Test: TTest;
  begin
    SafeCreateObject(TTest.Create(''Jerry''), Test);
    Test.SayHello;
  end;

  begin
    Proc1;
    try
      Proc2;
    except
      Writeln(''Catch you'');
    end;
    Proc3;
    Writeln(''Finished'');
    Readln;
  end.

以下是这个例子的输出:

  Hello, I am Tom       // Tom 创建
  Tom is gone           // 在 Jim 创建之前,Tom 释放了
  Hello, I am Jim       // Jim 创建,这行只出现一次说明异常正确抛出
  Jim is gone           // 即使出现异常,Jim 还是释放了
  Catch you             // 而且是在退出 Proc2 之前
  Hello, I am Jerry     // 演示另一种用法
  Jerry is gone         // 再次成功 :-)
  Finished

以下是具体实现单元文件(XhSafeObj.pas):

{******************************************************************************}
{                                                                              }
{                               Beta Code Library                              }
{                                                                              }
{                          Copyright (c) 2004-2004 Beta                        }
{                                                                              }
{       Author:         Beta Xiong                                             }
{       Creation:       2004-08-25                                             }
{       Version:        0.01                                                   }
{       file:           xhSafeObj.pas                                          }
{       Description:    Safe-object that will be destroyed automaticly when    }
{                       get out of current procedure, like C++ smart pointer.  }
{       WebSite:        http://www.01cn.net                                    }
{       EMail:          beta@01cn.net or xbeta@tom.com                         }
{       Update:                                                                }
{         2004-08-25    First creation                                         }
{                                                                              }
{******************************************************************************}

unit XhSafeObj;

interface

type
  ISafeObject = interface(IInterface)
  [''{4D1E5EFE-BE7A-C0DE-11B3-C0326DA03A05}'']
  end;

{ Usage:
    SomeObj := TSomeClass.Create(SomeParams);
    SafeObject(SomeObj);
  After that, the SomeObj will be destroyed automaticly when get out of current
  procedure.
}
function SafeObject(Instance: TObject): ISafeObject;

{ Usage:
    SafeCreateObject(TSomeClass.Create(SomeParams), SomeObj);
  After that, the SomeObj will be destroyed automaticly when get out of current
  procedure.
}
function SafeCreateObject(Cnstrctor: TObject; out Reference): ISafeObject;

implementation

type
  TSafeObject = class(TInterfacedObject, ISafeObject)
  private
    FInstance: TObject;
  public
    constructor Create(const Instance: TObject); virtual;
    destructor Destroy; override;
  end;

{ TSafeObject }

constructor TSafeObject.Create(const Instance: TObject);
begin
  FInstance := Instance;
end;

destructor TSafeObject.Destroy;
begin
  FInstance.Free;
  FInstance := nil;
end;

function SafeObject(Instance: TObject): ISafeObject;
begin
  Result := TSafeObject.Create(Instance);
end;

function SafeCreateObject(Cnstrctor: TObject; out Reference): ISafeObject;
begin
  Result := TSafeObject.Create(Cnstrctor);
  TObject(Reference) := Cnstrctor;
end;

end.

结合前面的例子稍微解释一下:

  procedure Proc1;
  var
    Test: TTest;
  begin
    Test := TTest.Create(''Tom''); // 创建一个实例
    SafeObject(Test); // 这里就是关键了,把这个实例指针传给了 SafeObject 函数
                      // 而 SafeObject 函数创建了一个 TSafeObject 对象,并且
                      // 把该实例指针保存了起来,那么在这个 TSafeObject 对象
                      // 释放时,就可以通过其析构函数释放掉这个 TTest 实例了。
                      // 那么那个 TSafeObject 对象是怎么释放的呢?往后看。
    Test.SayHello;

    // 要退出函数了,Delphi 管理接口指针是很老实的,对于声明为局部变量的接口
    // 指针,在退出函数前是会被自动释放的,呃,实际上是减少其引用计数。等等,
    // 局部变量的指针?哪里?别忘了我们刚才调用那个 SafeObject 函数实际上是有
    // 返回值的,它返回了一个 ISafeObject 指针(当然了,是由那个 TSafeObject
    // 实现的),我们没有保存这个返回值,而是把它丢掉了。这样,Delphi 会把那
    // 个返回的接口指针当成一个临时变量,它享有和局部变量同等的待遇,对于这种
    // 身为临时变量的接口指针同样会在过程结束时被释放。因此也就能够保证刚才的
    // TSafeObject 能释放,也就保证了刚才传入的那个对象的实例能够在过程退出时
    // 被释放。
  end;

  procedure Proc2;
  var
    Test: TTest;
  begin
    Test := TTest.Create(''Jim''); // 创建一个实例
    SafeObject(Test);
    Test.SayHello;

    // 虽然这里有异常,但是 Delphi 管理接口指针的机制决定了,局部接口指针即使
    // 是出现异常,也能自动释放。Delphi 会自动为整个过程增加一 try-finally 的
    // 包裹(当然,只在有必要的时候),在出现异常的时候,这个过程级的 fianlly
    // 会被执行,其中就包含清除这些局部接口指针的代码。所以即使出现异常,保存
    // 的对象实例同样能够被释放。
    raise Exception.Create(''Something wrong'');
    Test.SayHello;
  end;

也许你已经想到了,在 Delphi 中,离开过程会自动释放的东东不仅仅是接口一个,还有
变体类型 :-) 以下提供了基于变体类型的 SafeObject 实现,就不做解释了,一个道理。
基于变体类型实现的具体单元文件(XhSafeObjVar.pas):

{******************************************************************************}
{                                                                              }
{                               Beta Code Library                              }
{                                                                              }
{                          Copyright (c) 2004-2004 Beta                        }
{                                                                              }
{       Author:         Beta Xiong                                             }
{       Creation:       2004-08-25                                             }
{       Version:        0.01                                                   }
{       file:           XhSafeObjVar.pas                                       }
{       Description:    Safe-object that will be destroyed automaticly when    }
{                       get out of current procedure, like C++ smart pointer.  }
{       WebSite:        http://www.01cn.net                                    }
{       EMail:          beta@01cn.net or xbeta@tom.com                         }
{       Update:                                                                }
{         2004-08-25    First creation                                         }
{                                                                              }
{******************************************************************************}

unit XhSafeObjVar;

interface

uses
  Variants;

{ Usage:
    SomeObj := TSomeClass.Create(SomeParams);
    SafeObject(SomeObj);
  After that, the SomeObj will be destroyed automaticly when get out of current
  procedure.
}
function SafeObject(const Instance: TObject): Variant;

{ Usage:
    SafeCreateObject(TSomeClass.Create(SomeParams), SomeObj);
  After that, the SomeObj will be destroyed automaticly when get out of current
  procedure.
}
function SafeCreateObject(const Cnstrctor: TObject; out Reference): Variant;

implementation

type
  TSafeObjectVariantType = class(TCustomVariantType)
  public
    procedure Clear(var V: TVarData); override;
  end;

  TSafeObjectVarData = packed record
    VType: TVarType;
    Reserved1, Reserved2, Reserved3: Word;
    VObject: TObject;
    Reserved4: LongInt;
  end;

var
  SafeObjectVariantType: TSafeObjectVariantType = nil;

{ TSafeObjectVariantType }

procedure TSafeObjectVariantType.Clear(var V: TVarData);
begin
  TSafeObjectVarData(V).VObject.Free;
  TSafeObjectVarData(V).VObject := nil;
end;

function SafeObject(const Instance: TObject): Variant;
begin
  VarClear(Result);
  TSafeObjectVarData(Result).VType := SafeObjectVariantType.VarType;
  TSafeObjectVarData(Result).VObject := Instance;
end;

function SafeCreateObject(const Cnstrctor: TObject; out Reference): Variant;
begin
  VarClear(Result);
  TSafeObjectVarData(Result).VType := SafeObjectVariantType.VarType;
  TSafeObjectVarData(Result).VObject := Cnstrctor;
  TObject(Reference) := Cnstrctor;
end;

initialization
{$WARNINGS OFF}
  // no need to implement abstract method TCustomVariantType.Copy, so we turn
  // off the "Constructing instance of ''TSafeObjectVariantType'' containing
  // abstract method ''TCustomVariantType.Copy''" compiler message
  SafeObjectVariantType := TSafeObjectVariantType.Create;
{$WARNINGS ON}

finalization
  SafeObjectVariantType.Free;
  SafeObjectVariantType := nil;

end.

稍做解释:
这是 Delphi 自己规定的 CustomVariant 的实现机制决定的。它将自定义变体类型分为
操作和数据。其中所有对自定义变体的操作都转发到这个 TSafeObjectVariantType 的
Helper 上来,而所有的数据就被塞到一个 TVarData 中。TSafeObjectVariantType 在
创建的时候会自动注册该变体类型,并被分配一个 VType,以后凡事对 VType 为这个值
的变体的操作就会发送到这个 Helper 上来,当然,其中就包括 Clear。

据不严格测试,用接口的实现比用变体型的实现稍微快那么一点点 :-)

其实稍做修改就可以支持类似 BeginUpdate/EndUpdate 这样的自动调用,那仅仅是以一个
方法指针的传递取代对象引用的传递而已,而且又没有太大意义,就不去管了,呵呵。

OK,就写到这里,不管是砖板还是什么都可以扔过来,不过大过节的。。。希望不要人身
攻击,呵呵。

Tags:

作者:佚名

文章评论评论内容只代表网友观点,与本站立场无关!

   评论摘要(共 0 条,得分 0 分,平均 0 分) 查看完整评论
PB创新网ourmis.com】Copyright © 2000-2009 . All Rights Reserved .
页面执行时间:29,906.25000 毫秒
Email:ourmis@126.com QQ:2322888 蜀ICP备05006790号