Reference Counting

If you have looked through the simulator code. You may have noticed class names such as ProcessPtr, GuardPtr, ExpressionPtr, etc. Class names with the Ptr suffix are reference counted pointers to its class name prefix. For example, ProcessPtr is a reference counted pointer to the Process class. Many C++ books discuss reference counter pointers. Reference count may be called smart memory, smart pointers, handle classes, envelope classes, or referencing counting. Both Stroustrup and Coplien mention the topic. The smart pointer class used in the simulator is based on the handle class sketched in Stroustrup's book.

The simulator is large enough application that we need to be concerned about memory management. Many objects share references to the same object. But, the question is who frees the memory. With reference counting, the answer is simple. All the classes do. Not every pointer should be reference counted. If a class is the only class which references an object, then don't reference count. If the pointer is shared, consider reference counting.

One of the most powerful features smart pointers give to is automatic desruction of objects when the object go out of scope. Applications which throw exceptions (the simulator will be one) can use this to reclaim referred to by variables on the stack.

The smart pointer class used in the simulator gives "almost" pointer semantics. To create a smart pointer for a class named Box, use the macro Ptr to class the class.

Ptr(Box)

This macro expands into the class definition for the the class BoxPtr. The methods for the BoxPtr class are

BoxPtr() - construct a smart pointer. (The pointer to the object is nil in this case.)

BoxPtr(const BoxPtr&) - The copy constructor. Copy a smart pointer and increment the reference count.

BoxPtr(const BoxPtr*) - Create a smart pointer from a pointer to an object and make the reference count 1.

operator=(const BoxPtr&) - Assign one smart pointer to another. (Do the appropriate adjustment of the reference counts.)

operator=(const BoxPtr*) - Use an assignment statement to create a smart pointer with reference count of one.

~BoxPtr() - Destroy a smart pointer, decrement the reference  count, and if the reference count is zero destroy  the object.

operator*() - Dereference a smart pointer. If we have a variable p which is a BoxPtr, *p is a reference to an object  of type Box.  operator->() - Returns a pointer to a Box. This allows you to use the arrow syntax. So, you can write something like p->draw().

use() - Returns a pointer to type Box.

steal() - Returns a pointer to type Box. Set the reference  count to zero. You probably will never use this method.

This is a simple example demonstration smart pointers. It demonstrates  Object destruction when a variable goes out of scope. If Box were not smart pointered, the memory allocated by new Box would be leaked.

#include <iostream.h>
#include <Ptr.h>
class Box {
    public:
    Box() { cout << "Box is constructed" << endl; } ~Box() { cout << "Box is destroyed" << endl; }
};
Ptr(Box);  make a smart pointer for Box
    int main (int, char **) { cout << "BoxPtr x;\nBoxPtr y;\n\n";
    BoxPtr x; BoxPtr y;
    cout << "x = new Box;\n"; x = new Box;
    cout << "\nx = " << x  << "y = " << y << endl;
    cout << "y = x;\n"; y = x; cout << "\nx = " << x  << "y = " << y << endl;
    return 0;
}

BoxPtr x; BoxPtr y;
x = new Box; Box is constructed
x = Rep pointer: 0x20000
    Reference count: 1
    Last ref value: 0
y = Rep pointer: 0 (nil reference)
y = x;
x = Rep pointer: 0x20000
  
Reference count: 2
    Last ref value: 0
y = Rep pointer: 0x20000
    Reference count: 2
  
Last ref value: 0
Box is destroyed

Demonstrate how smart pointers can help clean up after an exception.

#include <iostream.h> #include <Ptr.h> class Box {
public:
Box() { cout << "Box is constructed" << endl; } ~Box() { cout << "Box is destroyed" << endl; } };
Ptr(Box);  make a smart pointer for Box
void sub2() { cout << "Throw an exception. Box should be destroyed\n";
throw "exception thrown from sub2()"; }
void sub1(void) { BoxPtr x;
 x = new Box;
 sub2(); }
int main (int, char **) {
try { sub1(); } catch ( char *p ) { cout << "Caught: " << p << endl; }
return 0; }

Box is constructed
Throw an exception.
Box should be destroyed
Box is destroyed
Caught: exception thrown from sub2()