9
votes

Here I have a tricky situation, I guess. I need to be able to free an object which is a field of a record. I would normally write the cleanup code in the destructor, if it was a class. But since record types can't introduce a "destructor", how would it be possible to call TObject(Field).Free; ?

There'll be two types of usage I predict:

  1. Replacing the record with a new one.

    I think this usage would be easy to implement. Since records are value types and so they are copied on assignment, I can overload the assigning operator and free the objects owned by old record.

    ( Edit: Assignment overloading wasn't able. That's a new info to me.. )

  2. Exiting the scope where record variable defined.

    I can think of a private method that frees the objects and this method could be called on scope excitation manually. BUT, here is the same question: How to make it more recordly? This behaviour kind of feels like a class...

Here is a sample (and obviously not the intended usage):

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;

Assume,

FAdditionalData:=TList<Pointer>.Crete;

called in record constructor or manually in record variable scope by accessing the field publicly like

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;

After exiting the ButtonClick scope the rec is no more but a TList still keeps its existance which causes to memory leaks...

2
Record assignment cannot be overloaded.kludg
I wasnt aware of that (never needed before), but I've learned it now :) Yeah, it wasnt able to be overloaded...Hasan Manzak

2 Answers

12
votes

If all you have in the record is an object reference, then you can't get the compiler to help you. You are in sole charge of the lifetime of that object. You cannot overload the assignment operator, and you don't get any notification of scope finalisation.

What you can do though is to add a guard interface that will manage the lifetime of the object.

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

You need to make sure that TMyObject manages its lifetime by reference counting. For example by deriving from TInterfacedObject.

When you initialise the record you do this:

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

At this point, the guard field of the record will now manage your object's lifetime.

In fact, if you want to push this idea further, you can build a dedicated class to guard the lifetime of objects. That then no longer constrains you to implement IInterface on your class. There are plenty of examples on the web that illustrate the technique. For example I offer Jarrod Hollingworth's article titled Smart Pointers, and Barry Kelly's titled Reference-counted pointers, revisited. There are many more out there. It's an old trick!

Note however, that what you have here is a strange hybrid of value type and reference type. On the face of it, records are value types. However, this one acts like a reference type. If you have other fields in the record that are value types then that would be even more confusing. You'll need to be very aware of this issue when you work with such a record.

On the face of it, without knowing more about your design, I'd be inclined to advise you not to put object references in records. They fit better inside reference types, i.e. classes.

3
votes

I remember that someone created a class named TLifetimeWatcher. Basically, it looks like:

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;

// The (cleanup) proc will be executed in the destructor if assigned, otherwise the instance will be freed by invoking the Free method.