9

我想,在这里我遇到了一个棘手的情况。我需要能够释放作为记录字段的对象。如果它是一个类,我通常会在析构函数中编写清理代码。但是由于记录类型不能引入“析构函数”,怎么可能调用TObject(Field).Free; ?

我预测会有两种用法:

  1. 用新的记录替换记录。

    我认为这种用法很容易实现。由于记录是值类型,因此它们在赋值时被复制,我可以重载赋值运算符并释放旧记录所拥有的对象。

    编辑:分配重载无法。这对我来说是一个新信息..)

  2. 退出定义记录变量的范围。

    我可以想到一个释放对象的私有方法,并且可以在范围激发上手动调用此方法。但是,这里有同样的问题:如何让它更记录?这种行为有点像上课……

这是一个示例(显然不是预期的用途):

TProperties = record
  ... some other spesific typed fields: Integers, pointers etc..
  FBaseData: Pointer;

  FAdditionalData: TList<Pointer>;
  //FAdditionalData: array of Pointer; this was the first intended definition
end;

认为,

FAdditionalData:=TList<Pointer>.Crete;

在记录构造函数中调用或通过像这样公开访问字段在记录变量范围内手动调用

procedure TFormX.ButtonXClick(Sender: TObject);
var
  rec: TProperties;
begin
  //rec:=TProperties.Create(with some parameters);

  rec.FAdditionalData:=TList<Pointer>.Create;

  //do some work with rec
end;

退出 ButtonClick 范围后,rec不再存在,但TList仍然存在,这会导致内存泄漏......

4

2 回答 2

12

如果您在记录中只有一个对象引用,那么您无法让编译器帮助您。您全权负责该对象的生命周期。您不能重载赋值运算符,并且您不会收到任何范围确定的通知。

你可以做的是添加一个保护接口来管理对象的生命周期。

TMyRecord = record
  obj: TMyObject;
  guard: IInterface;
end;

您需要确保TMyObject它通过引用计数来管理其生命周期。例如,从TInterfacedObject.

当你初始化记录时,你这样做:

rec.obj := TMyObject.Create;
rec.guard := rec.obj;

此时,guard记录的字段现在将管理您的对象的生命周期。

事实上,如果你想进一步推动这个想法,你可以构建一个专门的类来保护对象的生命周期。这样就不再限制您IInterface在课堂上实施。网上有很多例子可以说明这项技术。例如,我提供 Jarrod Hollingworth 的题为Smart Pointers的文章和 Barry Kelly 的题为Reference-counted pointers, revisited的文章。还有很多。这是老套路了!

但是请注意,您在这里拥有的是值类型和引用类型的奇怪混合。从表面上看,记录是值类型。然而,这个行为就像一个引用类型。如果您在记录中有其他字段是值类型,那么这将更加令人困惑。当您使用这样的记录时,您需要非常注意这个问题。

从表面上看,在不了解您的设计的情况下,我倾向于建议您不要将对象引用放入记录中。它们更适合内部引用类型,即类。

于 2012-10-12T14:41:59.677 回答
3

我记得有人创建了一个名为 TLifetimeWatcher 的类。基本上,它看起来像:

TLifetimeWatcher = class(TInterfacedObject)
private
  fInstance: TObject;
  fProc: TProc; 
public
  constructor Create(instance: TObject); overload;
  constructor Create(instance: TObject; proc: TProc); overload;
  destructor Destroy; override;
end;

// 如果分配了 (cleanup) proc,将在析构函数中执行,否则将通过调用 Free 方法释放实例。

于 2012-10-12T14:59:44.023 回答