2
votes

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.

2
Declaring TestObj: ITestInterface; will make you happy. Or, well, at least will manage to do what you expect.Victoria
It's amazing! why? Is it because of that TTestObj inherits from two classes, the compiler does not know which destroctor should be called ?DDGG
No. Its simply because the interfaced object is never destroyed. Hence it never gets a chance to destroy the objects it owns. My answer explains why.David Heffernan
TTestObject does not inherit from two classes: it inherits from TInterfacedObject and implements the interface. There is only one destructor. I don't know what @Victoria means here, but it seems pretty irrelevant.Rudy Velthuis
@Rudy, I believe OP expected code in the destructor to be executed. And, declaring that variable the way I've shown could make them happy.Victoria

2 Answers

6
votes

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.

4
votes

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).