Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is TInterfacedObject.Destroy called (a ScopedLock class)

i'm wondering when an instance of a TInterfacedObject derived class is destroyed and who calls the destructor. I wrote a ScopedLock class, which should automatically call the Release method of a synchronization object when the instance goes out of scope. It's an RAII concept known from C++, but i don't know if it is guaranteed that the destructor is called, when the lock instance goes out of scope.

ILock = interface
end;

ScopedLock<T: TSynchroObject> = class(TInterfacedObject, ILock)
strict private
    sync_ : T;
public
    constructor Create(synchro : T); reintroduce;
    destructor Destroy;override;
end;

implementation
{ ScopedLock<T> }

constructor ScopedLock<T>.Create(synchro: T);
begin
    inherited Create;;
    sync_ := synchro;
    sync_.Acquire;
end;

destructor ScopedLock<T>.Destroy;
begin
    sync_.Release;
    inherited;
end;

{ Example }
function Example.Foo: Integer;
var
  lock : ILock;
begin
  lock := ScopedLock<TCriticalSection>.Create(mySync);
  // ...
end;  // mySync released ?

It works fine in a simple test case, but is it safe?

like image 510
hansmaad Avatar asked Dec 08 '10 15:12

hansmaad


2 Answers

Yes, that is save. Your code

function Example.Foo: Integer;
var
  lock : ILock;
begin
  lock := ScopedLock<TCriticalSection>.Create(mySync);
  // ...
end;

is compiled as the following pseudocode

function Example.Foo: Integer;
var
  lock : ILock;
begin
  lock := ScopedLock<TCriticalSection>.Create(mySync);
  lock._AddRef;  // ref count = 1
  try
// .. 
  finally
    lock._Release;  // ref count = 0, free lock object
  end;

You can see that when lock var goes out of scope its ref count is decremented, became zero and lock object is automatically destroyed.

like image 59
kludg Avatar answered Nov 15 '22 02:11

kludg


The only problem is if the function is inlined: the local variable containing the ILock reference will be promoted to the scope of the function calling the inlined function. That may cause the lock to live longer than you wanted.

On the other hand, there's no need to actually declare a variable to hold the interface reference, if you write a function (e.g. a class function called Create) that returns an interface reference (as opposed to an object reference). The compiler will create a hidden local to receive the return value (as all managed types like interfaces and strings are actually returned by passing a result variable). This hidden local will act just like the explicit local.

I wrote more about it here: http://blog.barrkel.com/2010/01/one-liner-raii-in-delphi.html

like image 36
Barry Kelly Avatar answered Nov 15 '22 04:11

Barry Kelly