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"