Object lifetime with reference counting

Post your FreeBASIC tips and tricks here. Please don’t post your code without including an explanation.
coderJeff
Site Admin
Posts: 2695
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Object lifetime with reference counting

Postby coderJeff » Jun 02, 2018 12:25

This following code demonstrates:
1) Reference Counting for objects
2) Separating Interface from Implementation

By separating the interface from the data, then passing around the object either by value or by reference doesn't really matter, under the hood its all by reference. No more costly than passing pointers, although there is some overhead for incrementing/decrementing the reference count. That's the price to pay for letting your program manage the lifetime of the object.

I have placed many comments in the code to explain what each step is doing. The demo code is can be compiled and run, with the intent that you can examine the output alongside the program.

Code: Select all

'' ========================================================
'' Reference Counting Demo
''
'' This demo puts all the files in one listing only to make
'' compiling and testing easier.  Normally, I would put
'' each part in different headers and sources for reasons
'' which are explained below.
''
'' ========================================================
'' REFCOUNT.BI
'' ========================================================

'' REFERENCE COUNTING HELPER

'' the REFCOUNTER class tracks number of references on
'' another object.  It's code is reusable so it gets it's
'' own header/source
''
'' The REFCOUNTER object is a counter.  It's implemented
'' as a class/object so it can get initialized like an object
'' when used as a member of another TYPE created with
'' DIM or NEW.

#ifndef __REFCOUNT_BI_INCLUDE__
#define __REFCOUNT_BI_INCLUDE__

''
type REFCOUNTER
   declare constructor()
   declare destructor()
   declare function IncRef( byval p as any ptr ) as integer
   declare function DecRef( byval p as any ptr ) as integer
   refcount as integer
end type

#endif

'' ========================================================
'' REFCOUNT.BAS
'' ========================================================

'' REFERENCE COUNTING HELPER

'' !!! #include once "refcount.bi"

'' Implentation of the reference counter - doesn't do much
'' other than provide an interface.  Conceptually though,
'' an object, when created will have a reference count of
'' one (1), and as the object is assigned to other variables
'' parameters, etc, the reference count will increase.
'' As the references go out of scope, the reference count
'' will decrease by one (1), each time until the reference
'' count is zero (0), at which point the acutal object
'' data is deleted (destroyed).

''
constructor REFCOUNTER()
   refcount = 0
end constructor

''
destructor REFCOUNTER()
end destructor

''
function REFCOUNTER.IncRef( byval p as any ptr ) as integer
   '' the 'p' argument is optional, but could be used
   '' for degugging, memory leak detection, etc
   refcount += 1
   print "    IncRef(@" & hex(p) & ")"
   function = refcount
end function

''
function REFCOUNTER.DecRef( byval p as any ptr ) as integer
   '' the 'p' argument is optional, but could be used
   '' for degugging, memory leak detection, etc
   print "    DecRef(@" & hex(p) & ")"
   if( refcount ) then
      refcount -= 1
   end if
   function = refcount
end function

'' ========================================================
'' MYOBJECT.BI
'' ========================================================

'' MYOBJECT INTERFACE

#ifndef __MYOBJECT_BI_INCLUDE__
#define __MYOBJECT_BI_INCLUDE__

'' This "object" holds only a reference (pointer) to the
'' actual object data.  When assigning this object to a
'' variable or passing it byval or byref as a parameter
'' we are really only passing a reference (a pointer
'' reference that is).  This object is more like an
'' interface to an object with some extra code to
'' handle reference counting than an independant object.

'' the actual data is in MYOBJECT_CTX, but we are making
'' it really private, besides, it is not declared yet,
'' so just provide a forward alias.
type MYOBJECT_CTX_ as MYOBJECT_CTX

type MYOBJECT
   
   private:
      '' pointer to the actual object data - this
      '' (pointer) reference is the only data
      '' stored in the interface.
       
      ctx as MYOBJECT_CTX_ ptr

   public:

      '' Minimally, need to implement the following:
      '' constructor to allow:
      ''     DIM x as MYOBJECT
      '' copy constructor to allow:
      ''     DIM y as MYOBJECT = x
      '' assignment operator to allow:
      ''     x = y
      '' destructor:
      ''     clean-up when MYOBJECT goes out of scope

      declare constructor()
      declare constructor( byref rhs as const MYOBJECT )
      declare destructor()
      declare operator let ( byref rhs as const MYOBJECT )

      declare property text() as string
      declare property text( byref value as const string )

end type

#endif

'' ========================================================
'' MYOBJECT.BAS
'' ========================================================

'' MYOBJECT IMPLEMENTATION

'' This is the implementation of the MYOBJECT interface
'' and a module-private definition of the data that the
'' interface makes use of.

'' !!! #include once "refcount.bi"
'' !!! #include once "myobject.bi"

const NULL = cast(any ptr, 0)

'' --------------------------------------------------------
'' MYOBJECT_CTX - DATA
'' --------------------------------------------------------

'' MYOBJECT_CTX is the actual object data
type MYOBJECT_CTX
   refc as REFCOUNTER
   text as string

   '' ctor/dtor are optional, only for demo purposes
   declare constructor()
   declare destructor()
end type

''
constructor MYOBJECT_CTX()
   print "    new MYOBJECT_CTX() = data @" & hex(@this)
end constructor

''
destructor MYOBJECT_CTX()
   print "    delete MYOBJECT_CTX() = data @" & hex(@this)
end destructor

'' --------------------------------------------------------
'' MYOBJECT - INTERFACE
'' --------------------------------------------------------

''
constructor MYOBJECT()
   '' We are constructing an object and it is going to
   '' be refenced by either a variable, parameter,
   '' temporary type, so it needs a reference
   '' count of at least one (1).
   ''
   '' For Example: DIM x AS MYOBJECT


   ctx = new MYOBJECT_CTX
   print "    new MYOBJECT() ref @" & @this & " => data @" & hex(ctx)
   if( ctx <> NULL ) then
      ctx->refc.IncRef( ctx )
   end if

end constructor

''
constructor MYOBJECT( byref rhs as const MYOBJECT )
   '' We are constructing an object by
   '' making a copy of an existing object
   '' Keep in mind though, we are actually just
   '' constructing a reference to the real object
   '' data.  So, just copy the refence to the already
   '' created object and increment the reference
   '' count on it.

   ctx = rhs.ctx
   print "    new MYOBJECT() ref @" & @this & " => data @" & hex(ctx)
   if( ctx <> NULL ) then
      ctx->refc.IncRef( ctx )
   end if
end constructor

''
operator MYOBJECT.let( byref rhs as const MYOBJECT )

   '' We are assigning a MYOBJECT variable
   '' to another MYOBJECT variable - we need to
   '' decrement the reference counter on one,
   '' and increment the reference counter on the
   '' other.

   '' Don't do anything if THIS object and
   '' RHS both reference the same MYOBJECT_CTX

   if( ctx <> rhs.ctx ) then

      print "    assignment MYOBJECT() ref @" & @this & " => data @" & hex(rhs.ctx) & " (was @" & hex(ctx) & ")"

      '' DECREMENT REFERENCE COUNTER
      '' if THIS object reference is initialized,
      '' then decrement it's reference count because
      '' we are about to assign a reference to some
      '' other object (of the same type)

      if( ctx <> NULL ) then

         '' decrement the reference count and test
         '' if the reference count is zero

         if( ctx->refc.DecRef( ctx ) = 0 ) then

            '' reference count is zero, so, it is
            '' OK to actually release the object
            delete ctx

         end if
      end if

      '' ASSIGNMENT - actually we are just copying
      '' references.  We have already decremented
      '' the reference count for the expression on the
      '' left hand side of the expression.

      ctx = rhs.ctx

      '' INCREMENT REFERENCE COUNTER (on valid reference only)

      if( ctx <> NULL ) then
         ctx->refc.IncRef( ctx )
      end if

   end if

end operator

''
destructor MYOBJECT()
   '' Keep in mind that this is the destructor
   '' for the reference object only, the real
   '' object data is in ctx as MYOBJECT_CTX ptr.
   '' So, decrement the reference counter and only
   '' actually delete the object data if the
   '' reference count gets to zero (0).
   
   print "    delete MYOBJECT() ref @" & @this & " => data @" & hex(ctx)
      
   if( ctx ) then
      if( ctx->refc.DecRef( ctx ) = 0 ) then
         delete ctx
         ctx = NULL
      end if
   end if
end destructor

''
property MYOBJECT.text() as string
   property = ctx->text
end property

''
property MYOBJECT.text( byref value as const string )
   ctx->text = value
end property

'' ========================================================
'' DEMO.BAS
'' ========================================================

'' !!! #include once "myobject.bi"

print "Begin"
scope
   '' !!! #include once "myobject.bi"

   '' create an reference 'a' to a new MYOBJECT object
   '' using default constructor
   print "  dim a as MYOBJECT"
   dim a as MYOBJECT

   print "Begin B+C scope"
   scope
      

      '' create a reference 'b' that is same object as 'a'
      '' using copy constuctor
      print "  dim b as MYOBJECT = a"
      dim b as MYOBJECT = a

      '' create a reference 'c' to a new MYOBJECT object
      '' using default constructor
      print "  dim c as MYOBJECT"
      dim c as MYOBJECT

      '' now 'c' is also same object as 'a'
      '' using let operator
      print "  c = a"
      c = a

      '' setting text in one place is seen by all references
      c.text = "Only one data object"

      print "B:" & b.text
      print "C:" & c.text

      '' 'b' and 'c' go out of scope, the references are removed
      '' and the reference count is decreased on the actual object
      '' but it is not yet removed because 'a' still holds a
      '' reference to the data.
   
   end scope
   print "End B+C scope"

   print "A:" & a.text

end scope
print "End"

Return to “Tips and Tricks”

Who is online

Users browsing this forum: No registered users and 1 guest