DDGG
DDGG

Reputation: 1241

How to free a TObject member in a TInterfacedObject

I know an interfaced object is reference counted, so need not to manually free it. But if it has a TObject inherited member, should I manually free this member in the destructor?

Consider the following code:

program Project2;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Classes;

type
  ITestInterface = interface(IInvokable)
    ['{A7BDD122-7DC6-4F23-93A2-B686571AB2C8}']
    procedure TestMethod;
  end;

  TTestObj = class(TInterfacedObject, ITestInterface)
    constructor Create;
    destructor Destroy; override;
  private
    FData: TStrings;
  public
    procedure TestMethod;
  end;

{ TTestObj }

constructor TTestObj.Create;
begin
  FData := TStringList.Create;
end;

destructor TTestObj.Destroy;
begin
  Writeln('Destroy'); // This line won't apear in the console ouput as the destructor won't be called.
  FData.Free;         // Who guarantees this member will be freed ?

  inherited;
end;

procedure TTestObj.TestMethod;
begin
  Writeln('TestMethod');
end;

{ Main }

procedure Main;
var
  TestObj: TTestObj;
begin
  TestObj := TTestObj.Create;
  TestObj.TestMethod;
  TestObj := nil;     // TestObj should be freed at this moment ?
end;

begin
  Writeln('Program start!');
  Main;
  Writeln('Program end.');
  Readln;
end.

The program output:

Program start!
TestMethod
Program end.

It means the constructor was not been called, and the member FData was not been freed ?

What should I do ? Thanks in advance.

Upvotes: 3

Views: 1379

Answers (2)

David Heffernan
David Heffernan

Reputation: 613342

The code in TTestObj is fine. You must implement a destructor that destroys FData just as you have done.

The problem lies elsewhere. The interfaced object is not being destroyed because you never trigger any reference counting. You need to refer to the interfaced object via an interface variable. Replace

TestObj: TTestObj

with

TestObj: ITestInterface

Once you make this change, the interface reference code will add a reference when you first assign to the TestObj variable.


As an aside, you don't need this line

TestObj := nil

When the TestObj variable goes out of scope, the reference count will drop to zero and the implementing object will be destroyed.

Upvotes: 8

Rudy Velthuis
Rudy Velthuis

Reputation: 28826

You will have to free the TStrings in the destructor (unless you use an ARC compiler), as always.

The parent object (TTestObject) is freed automatically -- but only if it is used as interface --, but not the objects it references, like FData.

You are using the object as object, but you must use it as interface to get automatic reference counting:

var
  TestObj: ITestInterface;

But it is irrelevant whether TTestObject implements the interface or not, you must always free any aggregated objects in the destructor, as you do.

Again, the above is true only if you are using a compiler that does not implement ARC for objects (i.e. if you target Win32, Win64, macOS).

Upvotes: 4

Related Questions