------------------------------------------------------------------------------
MC logo
Reference Counting
[^] Tom's Lisp Code
------------------------------------------------------------------------------
[Classes for Lisp Atoms] [Functions for Lisp Built-Ins] [Classes for Closures] [Context] [Base For Evaluable Objects] [Callable Objects] [Interrupt Utility] [Main Program] [Pairs] [Code Reader] [Reference Counting]

Like Java, and unlike C, C++ and most traditional languages, Lisp uses garbage collection. The Tom's Lisp interpreter uses reference counting, probably the simplest form of GC. Each object counted contains a count of the number of pointers to it. RefCtObj is a base class for all memory-managed objects and holds the reference count. Class RefPtr behaves as a pointer to objects derived from RefCtObj. RefPtr controls the reference counts in the RefCtObj object they point to.

To control the reference counts in a controlled object, the count must be incremented whenever a new RefPtr object starts to point to that object, and must be decremented when that pointer is changed or destroyed. To make this happen, the RefPtr class has constructors, a destructor, and an assignment operator overload which manipulate the count inside the referenced object. If the count reaches zero, the object is deleted.

The template class Ptr<T> makes objects which behave as pointers to type T. This could be done with RefPtr alone, but that would require much casting.

Note that classed derived from RefCtObj should implement a static alloc method to create an object and return a Ptr for their own type T using the newRefPtr method. Clients should use this method to create new instances rather than using new, since ordinary C++ pointers will not maintain the counts.

//*****************************************************************************
//***  Reference counting.
//***
//***    This section provides a reference counting facility which is used 
//***    to manage all dynamically-allocated memory in the system.
//***    
//*****************************************************************************

/* Nodes and pointers to nodes.  There should be no way for the client
   to get a Node object except by Node::alloc, so there's no way to
   allocate one outside the management of NodePtr.  */

/* This is a base for any reference-counted object.  It holds the count, and
   has utilities for maniuplating it.  There is no public interface.  The
   reference counts are manipulated by the derived class, and by the smart
   pointer class which is a friend. */

#include <typeinfo>
#include <iostream>
using namespace std;

#ifndef _refct_h_
#define _refct_h_

class RefPtr;

class RefCtObj {
#ifdef MEM_DEBUG
protected:
        // This is a count of the number of object allocated.  Used for
        // debuggin when MEM_DEBUG is set at compile time.
        static int alocnt;

        // Controls reporting of allocation and deallocation.
        static int alo_rpt;
#endif
private:
        // This class describes pointers to objects of RefCtObj.
        friend class RefPtr;
        friend class DiscRefPtr;

        // How many pointers point to me.
        int refct;

        // Equality.  The equal just returns false if the types
        // differ, but calls the virtual (type-specific) equal_same
        // if the types match.
        virtual bool equal_same(RefCtObj& other) { 
                return false; 
        }
        bool equal(RefCtObj&);

protected:
        // Find the count.
        int curr_refct() { return refct; }

        // Increase the count.
        virtual void mkref() { 
#ifdef MEM_DEBUG
                if(alo_rpt > 2) cout << "Incre " << this << ": count "
                                     << refct << " -> " << refct + 1 << endl;
#endif
                ++refct; 
        }

        // Decrement the reference count, delete self if needed.
        virtual void unref() 
        {
#ifdef MEM_DEBUG
                if(alo_rpt > 2) cout << "Decr " << this << ": count "
                                     << refct << " -> " << refct - 1 << endl;
#endif
                if(--refct == 0)
                        delete this;
        }

        // Create a new one with zero count
        RefCtObj(): refct(0) { 
#ifdef MEM_DEBUG
                if(alo_rpt) cout << "Allocating " << this << " "
                                 << ": count "
                                 << alocnt << " -> " << alocnt + 1 << endl;
                ++alocnt;
#endif
        }

        // Copy cons resets count.
        RefCtObj(const RefCtObj& otr): refct(0) {
#ifdef MEM_DEBUG
                if(alo_rpt) cout << "Allocating " << this << " copy of "
                                 << &otr << " " << ": count "
                                 << alocnt << " -> " << alocnt + 1 << endl;
                ++alocnt;
#endif
        }

        // Create a RefPtr from an plain pointer to an arbitrary RefCtObj.
        // This exists mainly because it uses the private constructor of
        // RefPtr, which can do because of friendship.  So this method is
        // really a way for our descendents to use the private constructor
        // in RefPtr.
        static RefPtr newRefPtr(RefCtObj *created);

        // Make a new ref to self.  Just newRefPtr(this).
        RefPtr newref();

#ifdef MEM_DEBUG
        // Count it gone.
        virtual ~RefCtObj() { 
                if(alo_rpt) cout << "Freeing " << this << ": count "
                                 << alocnt << " -> " << alocnt - 1 << endl;
                --alocnt;
        }

public:
        // Report the number of blocks allocated.  Used for debugging.
        static void rpt(const char *msg) { 
                cout << msg << ": " << alocnt << " blocks allocated." << endl; 
        }

        // Control the value of the allocation reporting variable.
        static void reporting(int b) { alo_rpt = b; }
#endif
};

/* This is the smart pointer class.  It points to a type derived from
   RefCtObj, and uses its constructors to manage the reference count and
   delete objects as needed. */
class RefPtr {
        friend class RefCtObj;
protected:
        RefCtObj *target;               // The actual pointer.

        // Construct a reference to the object specifed by an ordinary ptr.
        RefPtr(RefCtObj *obj) {
                target = obj;
                if(target) target->mkref();
#ifdef MEM_DEBUG
                if(target && target->refct == 1) {
                        if(RefCtObj::alo_rpt)
                                cout << "Refed " << target << " " 
                                     << typeid(*target).name() << endl;
                } else {
                        if(target && RefCtObj::alo_rpt > 1)
                                cout << "ReRef " << target << " " 
                                     << typeid(*target).name()
                                     << target->refct - 1 << " -> " 
                                     << target->refct << endl;
                }
#endif
        }
public:
        // Public constructor creates the null pointer.
        RefPtr() { target = 0; }

        // Copy constructor increments the reference count of the object
        // pointed to.
        RefPtr(const RefPtr &r) {
                target = r.target;
                if(target) target->mkref();
#ifdef MEM_DEBUG
                if(target && RefCtObj::alo_rpt > 1)
                        cout << "CopyRef " << target << " " 
                             << typeid(*target).name() << " "
                             << target->refct - 1 << " -> " 
                             << target->refct << endl;
#endif
        }

        // Tell if this is the null pointer.
        bool isnull() { return target == 0; }

        // Set the pointer to null.  This reduces the reference count,
        // which may delete the object.
        void nullify() 
        { 
#ifdef MEM_DEBUG
                if(target && ((RefCtObj::alo_rpt && target->refct == 1) || 
                              RefCtObj::alo_rpt > 1))
                        cout << "Nullify " << target << " " 
                             << typeid(*target).name() << " "
                             << target->refct << " -> " 
                             << target->refct - 1 << endl;
#endif
                if(target) {
                        target->unref();
                }
                target = 0;
        }

        // Assign from another pointer.  This will reduce the ref count of
        // what we are pointing to now, and increase that of what we will
        // now point to.
        void assign(const RefPtr lft)
        {
                nullify();
                target = lft.target;
                if(target) target->mkref();
#ifdef MEM_DEBUG
                if(RefCtObj::alo_rpt > 1)
                        cout << "Assign " << target << " " 
                             << typeid(*target).name()
                             << target->refct - 1 << " -> " 
                             << target->refct << endl;
#endif
        }
        RefPtr & operator=(const RefPtr & lft) {
                assign(lft);
                return *this;
        }

        // Destroy it.  This must reduce the ref count of the object denoted.
        ~RefPtr() { 
#ifdef MEM_DEBUG
                if(RefCtObj::alo_rpt > 1 || 
                   (RefCtObj::alo_rpt == 1 && target && target->refct == 1))
                        cout << "Destroy ptr to " << target << endl;
#endif

                nullify(); 
        }

        // Test the target type.  Use like p.points_to(typeid(Fred)), where
        // Fred is a class.
        bool points_to(const type_info &tid) {
                if(target == 0) return false;
                return tid == typeid(*target);
        }

        // For debugging.
        void prt(ostream &s) { s << target; }

        // For implementing the eq? operation.  Just compares the target ptrs.
        bool eq(const RefPtr &r) { return target == r.target; }

        // Test that we point to an object with the same value (equal) to
        // the one r points to.  Used to implement equal?
        bool same_value(const RefPtr &r) { 
                return target == r.target || target->equal(*r.target); 
        }
};

/* These bodies belong to RefCtObj, but need to follow RefPtr because they
   allocate RefPtr objects.  */
inline RefPtr RefCtObj::newRefPtr(RefCtObj *created) 
{
        return RefPtr(created);
}
inline RefPtr RefCtObj::newref()
{
        return newRefPtr(this);
}
inline bool RefCtObj::equal(RefCtObj& other) {
        if(typeid(*this) != typeid(other)) return false;
        else return equal_same(other);
}

/* Note: All the classes descended from RefCtObj contain a method like this
   for allocation:

        // Allocate one and return a pointer.
        static Ptr<Fred> Fred::alloc(whatever) {
                return RefCtObj::newRefPtr(new Fred(whatever));
        }

   This is a static member of class Fred which allocates a Fred object but
   returns a Ptr<Fred> to refer to it, rather than a plain C++ pointer to
   Fred.  Fred does not have a public constructor, so it should be 
   impossible to get a plain pointer to a Fred object. */

/* This is adaptor that makes the smart pointer a bit easier to use with
   classes derived from RefCtObj.  It represents a pointer to any type T
   which is derived form RefCtObj.  It also contains an overload of -> which
   allows the use of methods of type T using the p->f() notation. */
template <class T>
class Ptr: public RefPtr {
public:
        // Construct.
        Ptr(): RefPtr() { }
        Ptr(const RefPtr &r): RefPtr(r) { }

        // Assign.  
        Ptr & operator=(const RefPtr & lft) { 
                assign(lft);
                return *this;
        }

        // Allow -> on Ptr<T> objects.  
        T * operator->() { return dynamic_cast<T*>(target); }

        // This should happen automatically, but it doesn't seem to.
        //~Ptr() { nullify(); }
};

#endif