среда, 25 февраля 2015 г.

Containers 1. Implementation of reference counting

Original in Russian: http://18delphi.blogspot.ru/2013/03/blog-post_4606.html

About containers. Table of contents

For already 15 years I’ve been thinking why Borland did not implement reference counting in base classes but created the confusing class - TInterfacedObject.

Well, I implemented. Long ago. And happily use it.

One should CLEARLY separate “reference counting” from IUnknown. The first one is a CONCEPT, the second one is ONE OF the IMPLEMENTATIONS of this concept.

I’ve organized reference counting so that you can’t write:

  List.Add(TItem.Create);

You can only write:
Item := TItem.Create;
  // the number of references is 1
  try
   List.Add(Item);
  // the number of references is 2
  finally
   FreeAndNil(Item);
  // the number of references is 1
end;

Many do not like it. But I do like. I like symmetry. Who owns the object, releases it.

In fact, there are “go-carts” for those who like “short form” and for the “fighters” for the number of lines. They have been taken from Objective-C:
List.Add(TItem.Create.Autorelease); 


Next, I’d like to tell about the implementation.

First, you should understand one simple thing: there is compiler-magic when classes are created.

From the code:
  A := TMyClass.Create;
something like this is compiled:
  A := TMyClass.NewInstance;
  A.Create;

and from the code:
  A.Destroy;

the following is compiled:
 A.Destroy;
 A.DestroyInstance

I.e. code for constructor is nothing but a code for initialization, and code for destructor is a code for deinitialization.

The memory for the object is allocated in NewInstance and freed in DestroyInstance. This could be found in the documentation, but who really reads it :-)

The diagrams:

The code:
RefCountedPrim.imp.pas:

{$IfNDef RefCountedPrim_imp}

{$Define RefCountedPrim_imp}
 _RefCountedPrim_ = {mixin} class(_RefCountedPrim_Parent_)
 private
 // private fields
   f_RefCount : Integer;
    {* The field for the properties of RefCount}
 protected
 // overridden protected methods
   procedure FreeInstance; override;
 public
 // overridden public methods
   destructor Destroy; override;
   class function NewInstance: TObject; override;
 protected
 // protected methods
   procedure Cleanup; virtual;
     {* The function of object fields cleaning. }
 public
 // public methods
   function Use: Pointer;
     {* increment the reference counter by 1 and return the pointer to itself. }
   function SetRefTo(var F): Boolean;
 public
 // public properties
   property RefCount: Integer
     read f_RefCount;
 end;//_RefCountedPrim_

{$Else RefCountedPrim_imp}

// start class _RefCountedPrim_

procedure _RefCountedPrim_.Cleanup;
begin
end;//_RefCountedPrim_.Cleanup

function _RefCountedPrim_.Use: Pointer;
begin
 if (Self <> nil) then
  InterlockedIncrement(f_RefCount);
 Result := Self;
end;//_RefCountedPrim_.Use

function _RefCountedPrim_.SetRefTo(var F): Boolean;
begin
 if (Pointer(F) = Self) then
  Result := false
 else
 begin
  Result := true;
  TObject(F).Free;
  Pointer(F) := Self.Use;
 end;//Pointer(F) = V
end;//_RefCountedPrim_.SetRefTo

destructor _RefCountedPrim_.Destroy;
begin
  if (InterlockedDecrement(f_RefCount) = 0) then
  begin
   Inc(f_RefCount);
   try
    try
     Cleanup;
    finally
     inherited Destroy;
    end;//try..finally
   finally
    Dec(f_RefCount);
   end;{try..finally}
  end;//InterlockedDecrement(f_RefCount) = 0
end;//_RefCountedPrim_.Destroy

class function _RefCountedPrim_.NewInstance: TObject;
begin
 Result := inherited NewInstance;
 _RefCounted_(Result).Use;
end;//_RefCountedPrim_.NewInstance

procedure _RefCountedPrim_.FreeInstance;
begin
 if (f_RefCount = 0) then
  inherited FreeInstance;
end;//_RefCountedPrim_.FreeInstance

{$EndIf RefCountedPrim_imp}


--------------------------------
RefCounted.imp.pas:



{$IfNDef RefCounted_imp}

{$Define RefCounted_imp}
 _RefCountedPrim_Parent_ = _RefCounted_Parent_;
 {$Include RefCountedPrim.imp.pas}
 _RefCounted_ = {mixin} class(_RefCountedPrim_)
 protected
 // protected methods
   destructor Destroy;
     {* To prevent being lured in overriding destroy. }
 end;//_RefCounted_

{$Else RefCounted_imp}

type _RefCountedPrim_R_ = _RefCounted_;

{$Include RefCountedPrim.imp.pas}

// start class _RefCounted_

destructor _RefCounted_.Destroy;
begin
 assert(false, 'We are not supposed to get here');
 inherited;
end;//_RefCounted_.Destroy

{$EndIf RefCounted_imp}

-------------------------------
Refcounted.pas:


unit Refcounted;

interface

type
 _RefCounted_Parent_ = TObject;
 {$Include RefCounted.imp.pas}
 TRefcounted = class(_RefCounted_)
 end;//TRefcounted

implementation

uses
  Windows
  ;

{$Include RefCounted.imp.pas}

end.

And the test:

RefcountedTest.pas:


unit RefcountedTest;
 
interface
 
uses
  BaseTest
  ;
 
type
 TRefcountedTest = class(TBaseTest)
 published
 // published methods
   procedure DoIt;
 end;//TRefcountedTest
 
implementation
 
uses
  Refcounted,
  SysUtils,
  TestFrameWork
  ;
 
// start class TRefcountedTest
 
procedure TRefcountedTest.DoIt;
var
 l_A : TRefcounted;
 l_B : TRefcounted;
begin
 l_A := TRefcounted.Create;
 try
  Check(l_A.RefCount = 1);
  l_B := l_A.Use;
  try
   Check(l_A.RefCount = 2);
   Check(l_B.RefCount = 2);
  finally
   FreeAndNil(l_B);
  end;//try..finally
  Check(l_A.RefCount = 1);
 finally
  FreeAndNil(l_A);
 end;//try..finally
end;//TRefcountedTest.DoIt
 
initialization
 TestFramework.RegisterTest(TRefcountedTest.Suite);
 
end.


... to be continued ...

Комментариев нет:

Отправить комментарий