如何自动释放类/对象?

12

Delphi应用程序中存在哪些自动释放对象的技术?

8个回答

19

使用接口而不是对象。它们是引用计数的,当引用计数达到0时会自动释放。


3
这个答案很好,点赞。但是在混合对象和接口时要非常小心,并确保不会在接口之间创建循环引用,因为它们将相互保持活动状态,从而导致内存泄漏。一个例子是一个带有子项的接口,每个子项都保持...... - mghie
一个指向它们父级的引用。虽然有绕过这个问题的方法(类型转换),但最好一开始就进行不同的设计。 - mghie
2
提醒一下:接口的实现会执行实际的引用计数,并且可以决定不使用它,就像TComponent一样。因此,使用接口并不总是意味着对象会自动销毁。 - Lars Truijens
是的,但我质疑这是否是良好的设计实践。在VCL的上下文中可以理解,但对于自己的设计来说,让接口的使用者承担所有权问题似乎不是一个好主意。最好坚持惯用语法,总有一种方法来实现它(适配器)。 - mghie

14

我编写了一个名为GC(垃圾回收)的函数,它接受一个对象并在执行退出当前方法时释放它。这有点像一个一行代码的缩写函数,用于一个Try Finally Free块。

与其写成:

procedure Test;
var AQuery: TQuery;
begin
  AQuery := TQuery.Create(nil);
  try
    ...
  finally
    FreeAndNil(AQuery);
  end;
end;

我只是拥有:

procedure Test;
var AQuery: TQuery;
begin
  AQuery := TQuery.Create(nil);
  GC(AQuery);
  ...
end;

GC函数只是以接口的形式返回一个对象。

function GC(obj: TObject): IGarbo;
begin
  Result := TGarbo.Create(obj);
end;

由于TGarbo类是从TInterfacedObject继承而来的,所以当TGarbo对象超出范围时,它将自动释放。在TGarbo对象的析构函数中,它还会释放您在其构造函数中传递的对象(您在GC函数中传递的对象)。

type
  IGarbo = interface
    ['{A6E17957-C233-4433-BCBD-3B53C0C2C596}']
    function Obj: TObject;
  end;

  TGarbo = class(TInterfacedObject, IGarbo)
  private
    FObj: TObject;
  public
    constructor Create(AObjectToGC: TObject);
    destructor Destroy; override;
    function Obj: TObject;
  end;

{ TGarbo }

constructor TGarbo.Create(AObjectToGC: TObject);
begin
  inherited Create;
  FObj := AObjectToGC;
end;

destructor TGarbo.Destroy;
begin
  if Assigned(FObj) then
    FreeAndNil(FObj);
  inherited;
end;

function TGarbo.Obj: TObject;
begin
  Result := FObj;
end;

由于困在Delphi 7的世界里,看不到在不久的将来升级到内置垃圾回收版本的希望,因此我沉迷于使用这种简便的方法轻松释放本地临时对象! :)


想象一下,如果您可以在变量声明旁边放置一个关键字来使这个过程自动化... - Lucas Steffen
我知道这是一个旧帖子,但是这个机制是如何工作的?过程Test调用函数GC,该函数返回一个IGarbo,其想法是当IGarbo稍后超出范围时,其析构函数将被调用,释放相关的TQuery。但是在Test中没有IGarbo变量可以将GC的结果分配给它。因此,假设“Result” IGarbo立即具有零引用计数,或者在GC()返回到Test时立即减少为零。因此,我不明白在从函数退出时如何调用IGarbo.Destroy。 - gwideman
回答我的问题:显然,Delphi 直到函数结束才删除 GC() 的未分配结果。这是 Delphi 的实现特异性,显然不适用于 FPC/Lazarus,正如在此讨论“自动对象删除”中所指出的 https://forum.lazarus.freepascal.org/index.php/topic,37524.0.html。 - gwideman

7

在接口方面,您可以尝试使用免费的Jedi Code Library中的JclSysUtils单元中的Guard函数。它允许您将对象与一个独立的接口引用相关联,因此当该接口引用被销毁时,该对象也会被销毁。当您无法修改正在使用的类以使其支持自己的接口时,这可能非常有用。

var
  G: ISafeGuard;
  foo: TStrings;
begin
  // Guard returns TObject, so a type-cast is necessary
  foo := Guard(TStringList.Create, G) as TStrings;
  // Use the object as normal
  foo.Add('bar');
end; // foo gets freed automatically as G goes out of scope

对象和通过GetMem分配的指针都有重载函数。此外,还有IMultiSafeGuard,可以确保多个对象被释放。

如果您有一个工厂函数,可能会创建一个对象,设置一些属性,然后返回它。如果在设置属性时出现异常,您将想确保释放该对象,因为您无法返回它。一种方法是像这样:

function Slurp(const source: TFileName): TStrings;
begin
  Result := TStringList.Create;
  try
    Result.LoadFromFile(source);
  except
    Result.Free;
    raise;
  end;
end;

使用 Guard 后,它将变成这样:
function Slurp(const source: TFileName): TStrings;
var
  G: ISafeGuard;
begin
  Result := Guard(TStringList.Create, G) as TStrings;
  Result.LoadFromFile(source);
  G.ReleaseItem;
end;
< p > ReleaseItem 方法撤销 ISafeGuard 对该对象的所有权。如果在此之前发生异常,则随着堆栈展开和接口释放,守护程序将释放该对象。


太完美了!我以前是创建目标类的子类,然后注入IInterface类型。你的方法非常清晰和最佳的。谢谢。 - Gedean Dias

6

我必须说,我不喜欢“隐藏”对象的自由。传统代码更好:

MyObject := TObject.Create;
try
  // do stuff
finally
  FreeAndNil(MyObject);
end;

没有任何问题,一切都按预期运作,并且人们能够识别模式。

如果你不喜欢“隐藏”对象的Free,那么为什么不直接调用Free而要借助FreeAndNil来隐藏它呢? - Rob Kennedy
2
在使用FreeAndNil()释放对象之后,MyObject将变为nil。如果在此之后尝试访问MyObject,则会立即出现访问冲突。如果你的MyObject不是全局对象且仅由一个线程使用,那么无需调用FreeAndNil(),而是直接使用Free()即可。 - inzKulozik
3
从技术上讲,我同意inzKulozik的观点,但我仍然在释放对象时使用FreeAndNil,因为这是一个良好的习惯。即使您的对象是局部变量并且在FreeAndNil之后没有被使用,也无从知晓将来有人可能会向您的方法中添加一些更多的代码,并在此之后使用它。 - CodeAndCats
我完全同意Ben的观点。首先,这是一个容易养成的习惯,代码也会随之增加。 - mj2008

5
使用VCL提供的组件对象所有权。只要您使用非nil所有者创建对象,就不需要显式释放它们。另请参阅我对问题的答案。

2
这里是 Delphi 的 Boehm 垃圾回收器 DLL 的 API。Delphi 的 API 由 Barry Kelly 编写,他现在在 CodeGear 公司编写编译器。

1

这属于接口的使用范畴。 - mghie
仍然值得一提,因为它是一个特定的实现,恰好使用接口。对于一些人来说,更容易理解智能指针,而不必直接重构所有内容以使用接口。 - skamradt
1
同意。不过,接口的一些陷阱(比如必须避免循环引用)在智能指针中也同样适用。 - mghie

-1

如果您使用Delphi for .Net / Delphi Prism,您将获得垃圾回收功能,它会处理所有内存释放。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接