释放Interfaced对象

时间:2015-11-06 12:49:02

标签: delphi pointers delphi-xe2

我对释放Interfaced对象表示怀疑。

我的界面如下所示:

type IBase = interface(IInterface)
    function test: Boolean;  
end;

这个其他类在所有接口类中都有一些我想要的属性:

type TBase = class(TInterfacedObject)
    protected
        FQuery: TADOQuery;
        FADOConnection: TADOConnection;
    public
        constructor Create; virtual; abstract;
        destructor Destroy;
    end;

我得到了一些继承自前一个类的clases,并且还实现了接口。

type TExample= class(TBase, IBase)
    public
        function test: Boolean;
        destructor Destroy;
end;

所以,使用这个模式,我可以使用这样的类:

procedure someProcedure(aux : IBase);
begin
    aux.test; //Aux is an instance of TExample and I'm using the interfaced method
end;

我的问题是,我如何销毁这个作为IBase interfacedObject的辅助对象? 我试过这个东西,先检查一下这不是一个零对象:

(aux as TObject).Destroy; //Invalid Pointer operation
(aux as TInterfacedObject).Destroy; //Invalid Pointer operation
(aux as TExample).Destroy; Also invalid Pointer operation!!??

我没有释放对象,而是读取它,因为继承自TInterfacedObject并实现了接口,我应该使用它:

aux := nil;

参考计数器将完成魔术,但使用ReportMemoryLeaksOnShutdown:= True;在我的项目中有一些泄漏,代码永远不会到达析构函数的断点。

我错过了什么吗?

修改

我改变了我的构造函数,现在就像:

type TBase = class(TInterfacedObject)
    protected
        FQuery: TADOQuery;
        FADOConnection: TADOConnection;
    public
        constructor Create; 
        destructor Destroy; override;
    end;


type TExample= class(TBase, IBase)
    public
        function test: Boolean;
end;

现在我想这更有意义,因为TBase正在分配和释放对象,而TExample类继承了析构函数。

3 个答案:

答案 0 :(得分:4)

不要手动释放界面对象!

当一个类派生自TInterfacedObject时,它会自动引用计数,并且只要没有接口引用就会自动释放。

这是什么意思?

以下过程通过其接口TExample引用IBase的实例。当从堆栈中删除Obj变量时,它会自动清除内存。

procedure Foo;
var
  Obj: IBase;
begin
  Obj := TExample.Create; // reference count will be set to 1 
  Obj.test;
end; // reference count will be set to 0 and Obj will be freed 

下一个过程在其类名上引用TExample的实例。此处的引用计数无效。编译器不包括对_AddRef_Release的调用。所以这个程序会泄漏内存:

procedure Foo;
var
  Obj: TExample;
begin
  Obj := TExample.Create; 
  Obj.test;
end; // Obj will not be freed automatically

所以你需要自己清理堆:

procedure Foo;
var
  Obj: TExample;
begin
  Obj := TExample.Create; 
  try
    Obj.test;        
  finally
    Obj.Free;
  end;
end; 

Obj传递时,它可以危险。只要对象的引用存储在接口引用中。让我们看看:

procedure Bar(Obj: IBase);
begin
  //...
end;

procedure Foo;
var
  Obj: TExample;
begin
  Obj := TExample.Create; 
  try
    Bar(Obj); 
    Obj.test; // Access violation!       
  finally
    Obj.Free;
  end;
end; 

这里会发生什么?

创建

Obj并将其存储为类引用。引用计数为0.调用Bar(Obj)时,对象将存储在接口引用中。编译器包括在调用_AddRef时调用_ReleaseBar。参考计数器将增加和减少,因此它再次变为0并且对象会自行销毁。

如何处理?

  • 不要手动释放界面对象!
  • 让引用计数为您完成工作。
  • 不要通过其具体类来存储TInterfacedObject的派生。
  • 仅通过其实施的界面存储它们。
  • 避免接口对象之间的循环引用。 Delphi中没有垃圾收集器!

答案 1 :(得分:1)

你的例子有些奇怪。

由于您已将TBase构造函数声明为virtual abstract,因此必须在TExample类中声明构造函数。

正如其他人所说,你必须将override指令添加到析构函数中。

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  FastMM4,
  System.SysUtils, ADODB, ActiveX;

type
  IBase = interface(IInterface)
    function test: Boolean;
  end;

  TBase = class(TInterfacedObject)
    protected
      FQuery: TADOQuery;
      FADOConnection: TADOConnection;
    public
      constructor Create; virtual; abstract;
      destructor Destroy; override;
  end;

  TExample= class(TBase, IBase)
    public
      function test: Boolean;
      constructor Create; reintroduce; virtual;
      destructor Destroy; override;
  end;

{ TBase }

destructor TBase.Destroy;
begin
  FQuery.Free;
  FADOConnection.Free;
  inherited;
end;

{ TExample }

constructor TExample.Create;
begin
  //inherited;
  FADOConnection := TADOConnection.Create(nil);
  FQuery := TADOQuery.Create(nil);
end;

destructor TExample.Destroy;
begin
  inherited;
end;

function TExample.test: Boolean;
begin
  Result := False;
end;


var
  example: IBase;

begin
  CoInitialize(nil);

  example := TExample.Create;
  try
    WriteLn(example.test);
  finally
    example := nil;
  end;

  CoUninitialize;
end.

另一件看起来很奇怪的事情是释放基础析构函数中的FADO对象,因为它们是在派生类中创建的。

作为旁注,我更喜欢这样的设计:

type
  TBase = class(TInterfacedObject)
    protected
      FQuery: TADOQuery;
      FADOConnection: TADOConnection;
    public
      constructor Create;
      destructor Destroy; override;
  end;

  TExample= class(TBase, IBase)
    public
      function test: Boolean;
      constructor Create;
      destructor Destroy; override;
  end;

{ TBase }

constructor TBase.Create;
begin
  inherited;
  FADOConnection := TADOConnection.Create(nil);
  FQuery := TADOQuery.Create(nil);
end;

destructor TBase.Destroy;
begin
  FQuery.Free;
  FADOConnection.Free;
  inherited;
end;

{ TExample }

constructor TExample.Create;
begin
  inherited;
  . . .
end;

答案 2 :(得分:-2)

这里有一些问题:

首先,您需要将TBase添加到析构函数中,如注释中所指出的那样。但是constructor的声明真的是杀死整个事情:

你有一个虚拟的空TInterfacedObject,它隐藏了TObject中的那个。除此之外,当你有一个type TBase = class(TInterfacedObject) protected FQuery: TADOQuery; FADOConnection: TADOConnection; public constructor Create; virtual; abstract; destructor Destroy; end; 时,你应该使用界面而不是实例。

所以

  TBase = class(TInterfacedObject)
  protected
    FQuery: TADOQuery;
    FADOConnection: TADOConnection;
  public
    constructor Create; reintroduce; virtual;
    destructor Destroy; override;
  end;

应该是

reintroduce

注意 procedure TForm1.FormCreate(Sender: TObject); var aux: TExample; begin aux := TExample.Create; aux.Free; end;

有了这个,并且覆盖添加到析构函数中,您可以释放对象,因为您习惯于:

procedure TForm1.FormCreate(Sender: TObject);
var
  aux: iBase;
begin
  aux := TExample.Create;
  //do stuff with aux 
end;

无需施放。在这里你创造了一个你自己的意志,你必须自己解放

但正如我之前所说的,当你有一个TInterfacedObject时,你应该使用该接口,当它不再被引用时,该对象将释放它自己。

所以前面的例子应该是这样的:

type
  IBase = interface(IInterface)
    function test: Boolean;
  end;

  TBase = class(TInterfacedObject)
  protected
    FQuery: TADOQuery;
    FADOConnection: TADOConnection;
  public
    constructor Create; reintroduce; virtual;
    destructor Destroy; override;
  end;

  TExample = class(TBase, IBase)
  public
    function test: Boolean;
    destructor Destroy; override;
  end;

  { TBase }

constructor TBase.Create;
begin
  inherited;
end;

destructor TBase.Destroy;
begin
  inherited;
end;

{ TExample }

destructor TExample.Destroy;
begin
  test;
  inherited;
end;

function TExample.test: Boolean;
begin
  ShowMessage('');
end;

我的完整测试代码如下所示:

procedure TForm1.FormCreate(Sender: TObject);
var
  aux: iBase;
begin
  aux := TExample.Create;
end;

然后打电话给它:

k = input("Please input something") # e.g. test
dict = {}
dict[k] = "This is my input"
print(dict) # prints {'test': 'This is my  input'}