面向对象: 接口与对象生存周期,对象能够自动释放吗? (10分)

L

lich

Unregistered / Unconfirmed
GUEST, unregistred user!
熟悉C++的都知道,在C++中局部对象在离开作用域后,会自动销毁
通常不用我们一直记着去用程序释放它,

但是在Delphi中,对象通常是需要有"人"来释放它的,
通常TComponent和它的子代们都会被他们的Owner释放,
一般的对象通常也会被他们的创建者释放,
如果一旦没有释放,就会有潜在的危险,发生内存泄漏,
长时间运行的程序会慢慢吃掉服务器的大量内存,直到他们被重新启动

于是,我开始怀念C++中的对象自动释放的好处来了,
喜欢偷懒的程序员不喜欢处理"后事"的
我向一位据说是Delphi高手的大侠请教,说起这点不爽的地方
说Delphi要是也支持这种特性就好了,

高手说话了:
其实,Delphi本来就支持这种自动的对象释放特性
但是必须使用接口,接口通常有一个引用计数,
计数减小到0时,对象会自动释放自己
定义一个局部变量的接口引用变量,
在退出它的作用域后,语言的底层实现中,
会减小此接口的对象的引用计数,如果对象没有被引用了,
就会自动释放掉了

高手还给我写了个例子程序,拿出来与大家共鉴

定义一个支持接口的对象,同时实现引用计数功能
unit Unit2;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;

type
{**访问Form2方法的接口}
IForm2 = interface(IInterface)
{按下Ctrl + Shift + G 生成接口的 GUID}
['{F4094D7B-9034-4F4D-A1BB-4F114E749F33}']
procedure ShowForm
stdcall;
procedure HideForm
stdcall;
end;
{**能自动释放的对象定义,一个普通的窗体}
TForm2 = class(TForm, IInterface, IForm2)
private
{ Private declarations }
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID
out Obj): HResult
override
stdcall;
function _AddRef: Integer
stdcall;
function _Release: Integer
stdcall;
procedure ShowForm
stdcall;
procedure HideForm
stdcall;
public
procedure AfterConstruction
override;
procedure BeforeDestruction
override;
class function NewInstance: TObject
override;
property RefCount: Integer read FRefCount;
end;

var
Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.AfterConstruction;
begin
InterlockedDecrement(FRefCount);
end;

procedure TForm2.BeforeDestruction;
begin
if RefCount <> 0 then
raise EInvalidPointer.Create('Invalid ptr');
end;

function TForm2._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;

function TForm2._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;

class function TForm2.NewInstance: TObject;
begin
Result := inherited NewInstance;
TForm2(Result).FRefCount := 1;
end;

function TForm2.QueryInterface(const IID: TGUID
out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;

procedure TForm2.HideForm;
begin
Hide;
end;

procedure TForm2.ShowForm;
begin
Show;
end;

end.

同时,将Unit2加入Unit1的头部的引用单元列表中(Uses)
在Button1的OnClick事件中写入代码:
var
f2: IForm2;
begin
f2 := TForm2.Create(nil) as IForm2;
f2.ShowForm;
Sleep(2000);

运行程序,按下Button1,会看到一个窗体出现,2秒钟后它消失了
这就实现了自动释放的功能

更复杂一点,在Form1中定义一个成员变量,是一个IForm2型的接口
则对象能保持到程序退出时,Form1被释放时自动释放

代码如下:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Unit2, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
ff2: IForm2;
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
f2: IForm2;
begin
f2 := TForm2.Create(nil) as IForm2;
f2.ShowForm;
ff2 := f2;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
ff2 := nil;
end;

end.
 
C

coolbaby

Unregistered / Unconfirmed
GUEST, unregistred user!
占个座先,有时间再讨论
 

远帆

Unregistered / Unconfirmed
GUEST, unregistred user!
接口的生存期机制是与Component的生存期机制相矛盾的。
乱用会有危险。
 
Z

zLight

Unregistered / Unconfirmed
GUEST, unregistred user!
最近的新书《Delphi 面向对象编程思想》里有这方面的介绍。
第6章 剖析接口/接口的其它用法探索/接口管理对象生命期
unit mtReaper;

interface
type
ImtReaper=interface
['{F3E97960-3F35-11D7-B847-001060806215}']
end;

TmtReaper=class(TInterfacedObject,ImtReaper)
private
FObject:TObject;
public
constructor Create(AObject:TObject);
destructor Destroy;override;
end;

implementation

uses SysUtils;

constructor TmtReaper.create(AObject:TObject);
begin
FObject:=AObject;
end;

destructor TmtReaper.Destroy;
begin
FreeAndNil(FObject);
Inherited;
end;

end.

unit frmMain;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TNoisyDeath = class(TObject)
public
destructor Destroy;override;
end;

TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
procedure WaitAWhile;
public
{ Public declarations }
end;



var
Form1: TForm1;

implementation
uses mtReaper;
{$R *.dfm}
destructor TNoisyDeath.Destroy;
begin
showMessage('对象销毁了!');
inherited;
end;

procedure TForm1.WaitAWhile;
var i:Integer;
begin
for i :=0 to 5000 do
begin
caption:=Inttostr(i);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var NoisyDeath:TNoisyDeath;
begin
NoisyDeath:=TNoisyDeath.create;
try
waitAWhile;
finally
NoisyDeath.Free;
end;

end;

procedure TForm1.Button2Click(Sender: TObject);
var
NoisyDeath:TNoisyDeath;
girm:ImtReaper;
begin
NoisyDeath:=TNoisyDeath.create;
girm:=TmtReaper.Create(NoisyDeath);
waitAWhile;
end;

end.
 
L

lance2000

Unregistered / Unconfirmed
GUEST, unregistred user!
你在c++里new一个对象难道不用delete?
object pascal里需要显示释放对象是因为:
1。object pascal里没有GC机制。
2。对象是创建在堆上的。
 
L

lich

Unregistered / Unconfirmed
GUEST, unregistred user!
是的,必须注意被重复释放的情况

我上面的例子程序中,如果Button1中的代码改为:
var
f2: IForm2;
begin
f2 := TForm2.Create(Self) as IForm2;
f2.ShowForm;
ff2 := f2;

程序退出时就会出错,
这就是 远帆,所说的乱用这种机制会出现的错误
 

远帆

Unregistered / Unconfirmed
GUEST, unregistred user!
动态创建的时候要好些,如果是拖放到窗体上的控件,问题就很大。
都要怪Delphi Vcl自做聪明的调用_AddRef和_Release方法,虽然有变通的方法,
不过容易给初学者和其它编程人员带来困扰。
 
L

lich

Unregistered / Unconfirmed
GUEST, unregistred user!
怎么根据一个对象的属性和方法,快速的生成一个供调用的接口呢?
 
L

lich

Unregistered / Unconfirmed
GUEST, unregistred user!
接受答案了.
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
I
回复
0
查看
552
import
I
顶部