Main Page   Modules   Class Hierarchy   Compound List   File List   Compound Members   File Members   Related Pages  

Reference Counting

Reference-count garbage collection is a powerful technique for managing memory that helps prevent objects from being deleted accidentally or more than once. The technique is not limited to C++ code and, despite its name, is unrelated to the C++ concept of reference variables. Rather, the term means that we maintain a count of all ``owning references'' to an object and delete the object when this count becomes zero.

Garbage collection in Iotr is accomplished by the collaboration of two classes, IotrRefCount and Handle. IotrRefCount is a base class of all reference-counted classes (i.e., most classes) in Iotr. Handle is a class whose instances act like pointers that manage their reference count automatically. The use of the Handle template class to manage reference counts is ubiquitous in the Iotr code, and is highly recommended.

Basics

Suppose MyClass is a subclass of IotrRefCount.

class MyClass : public IotrRefCount {
  ...
};
When an instance of MyClass is created, it is given a reference count of 1.
MyClass * m = new MyClass;
std::cout << m->refs() << std::endl; // will write "1"
Objects may increment the reference count to announce that they too are maintaining a reference to the object pointed to by m.
int oldRefs = m->refs();
anotherObject->someMethod( m );
if( m->refs() > oldRefs + 1 ) {
   // anotherObject is now maintaining a reference to m
} else {
   // anotherObject declined to maintain a reference to m
}
The object pointed to by m will not be released until all objects relinquish their references to m. One way that this can be done is through a call to IotrRelease.
IotrRelease( &m ); // m's reference count is decremented. If becomes 0
                   // then m is deleted.
This is, however, an atypical way of releasing the object. See the Handles section for a discussion of the recommended technique for manipulating the reference count.

Calling Conventions

For most method calls, other than constructors, the issue of reference counting never arises. Whenever possible, parameters and return values are C++ "reference variables". For example, the following code fragment defines methods that either return a reference to an object or take a reference as an argument:

class MyOtherClass : public IotrRefCount {
  ...
  virtual MyClass & getObject();
  virtual void transform( MyClass & m );
};

Note:
The phrase "reference variable" is unfortunate, because it is unrelated to the concept of reference counting. Nonetheless, the terms "reference counting" and "reference variable" seem to be standard and there doesn't appear to be a viable alternative terminology.
The reference-counting code has been designed so that it is only syntactically possible to increment or decrement the reference count of an object bound to a pointer variable. When the parameter of a routine is a C++ "reference variable," Iotr will never use that variable to establish an owning reference to the object (which it could do by taking the address of the variable.) Thus, a call such as
MyClass * m = new MyClass;
anotherObject->transform( *m );
will leave the reference count of m untouched. Similarly, in a call such as
MyClass & n = anotherObject->getObject();
the calling function must not use the variable n to alter the reference count of the object returned from MyClass::getObject. One must be careful that anotherObject is not deleted within the scope of the variable n, or else n will likely refer to a deleted member of anotherObject.

There are occasions when it is desirable to pass a pointer as a parameter or return a pointer from a function so that the reference count may be manipulated. It is common to use pointers as parameters to constructors.

class MyClass : public IotrRefCount {
  MyClass( MyOtherClass * obj ); // constructor taking a pointer as parameter
  ...
};
Suppose then, that the constructor is invoked as in the following code.
MyOtherClass * anotherObject  = new MyOtherClass;
MyClass * anObject            = new MyClass( anotherObject );
The MyClass::MyClass constructor must increment the reference count of anotherObject if the new instance of MyClass maintains a reference to the object pointed to by anotherObject. The routine may also decline to establish such a reference and leave the reference count untouched. The calling routine may not assume that the reference count will be incremented by MyClass::MyClass.

Pointers are used as return values of methods primarily when a new object is returned as the result of some calculation.

class MyOtherClass : public IotrRefCount {
  ...
  MyClass * newRelatedObject()
  {
     return new MyClass( this );
  }
};
Functions that call MyOtherClass::newRelatedObject must decrement the reference count of the new instance of MyClass or the object will never be deleted. A discussion of the recommended technique for incrementing and decrementing the reference count of an object may be found in the section discussing Handles.

Note that the following code is acceptable.

{
  MyClass p;
  anotherObject->transform( p );
}
In this code fragment p is created on the stack as an "automatic variable" and is deleted when it goes out of scope, regardless of its reference count. This idiom is useful for creating temporary objects where the code required to manage the reference count is cumbersome. Creating reference-counted objects as automatic variables will be safe as long as you never take the address of these variables. On the other hand, code that does take the address of these variables is almost guaranteed to generate bugs. Consider for example the following.
{
  MyClass p;
  anotherObject->keep( &p ); // Don't do this!
}
When the block exits, the object pointed to by p will be deleted, and the reference kept by anotherObject will become invalid. The invalid reference will almost certainly cause the program to crash at some other point in the code.

Handles

A handle is an object that acts like a pointer that automatically manages the reference count of the object it points to. Handle is a template class. Suppose we define MyClass as a subclass of IotrRefCount.

class MyClass : public IotrRefCount {
  ...
  void method1();
};
A handle for an instance of IotrRefCount may be declared as follows.
Handle<MyClass> object;
In the Iotr code, it is more common to provide a synonym to Handle<MyClass> by means of a typedef.
typedef Handle<MyClass> MyClassHandle;
MyClassHandle handle;
All reference-counted classes in Iotr have a similar typedef in the header file that defines the class. Consider the following code.
{
   MyClassHandle handle( new MyClass );
   ...
}
A object of type MyClass is created and bound to the variable handle. Immediately before the block ends and the variable handle ceases to exist, the reference count of the object bound to handle is automatically decremented. If the reference count of the object becomes zero, the object is deleted.

In method calls, handles may be used exactly as if they were pointers of type (MyClass *).

class MyClass : public IotrRefCount {
public:
   void method1();
   void method2(MyClass &);
   void method3(MyClass *);
};
...
MyClassHandle object( new MyClass ), object2( new MyClass );
object->method1();
object->method2( *object2 );
object->method3( object2 );

Sometimes it is necessary to bind an object to an existing handle, rather than create a new handle. When the object is returned from a method, or by operator new, a handle may be rebound as follows.

MyClassHandle object; // Refers to nothing
if( object ) {
  // Can't get here. "object" is nil and so evaluates as false.
}
...
// Rebind the variable "object"
object = MyClassHandle( new MyClass );
if( object ) {
  // Always get here. "object" is not nil and so evaluates as true.
}
It is also possible to use the same syntax to rebind a handle that already bound to another object. The reference count previously bound object is decremented appropriately.

When an object is passed into a routine as a parameter, a different syntax must be used. This is most typically done in constructors, as in the following example.

class MyOtherClass : public IotrRefCount {
protected:
  MyClassHandle myC;
public:
  MyOtherClass::MyOtherClass( MyClass * c )
  {
    HandleReferTo( myC, c );
  }
};
The HandleReferTo function increments the reference count of c to indicate that a new reference to the object has been established. There is also a special syntax to be used when a pointer to an object is returned from a function.
void MyClass::newObject()
{
   MyClassHandle handle( new MyClass );
   ...
   return HandleAsPointer( handle );
}
No special syntax is needed if the object is returned as a C++ reference.
MyClass & MyClass::getMyC()
{
   return *myC;
}
Variables of the same handle class may be assigned to one another, as in the following code fragment.
MyClassHandle object1(new MyClass), object2(new MyClass );
object1 = object2;
This rarely occurs in practice, primarily because none of the public methods in Iotr use handles as parameters, or return handles. We encourage new code to abide by this convention.

Idioms

In this section, we describe several common idioms involving handles. It is very common to create an object, and then pass it to the constructor of another object, as follows.

MyClassHandle handle( new MyClass );
MyOtherClassHandle otherHandle( new MyOtherClass( handle ) );
It is not possible to omit the first assignment to handle. Don't do the following.
// Don't do this
MyOtherClassHandle otherHandle( new MyOtherClass( new MyClass ) );
The code fragment above will certainly result in a memory leak, as the reference count of the MyClass object will never become zero.

On the other hand, it is possible to avoid assignment of an object to a handle when the object is to be immediately returned from a function.

void MyClass::newObject()
{
   return new MyClass;
}
If there is intervening code between the creation of the new instance of MyClass and the return statement, then it is advisable to assign the new object to a handle, rather than to a pointer. Otherwise, the MyClass object might not be deleted if an exception is thrown.

It is common to assign the value of a handle to a C++ reference variable to simplify syntax.

MyClassHandle hObject( anotherObject->newMyClass() );
MyClass & object = *hObject;

As noted above, it is not necessary to create a handle for temporary, local variables. These may safely be allocated as "automatic variables" on the stack, so long as you never use operator & to take the address of the object. Thus temporary objects may be created using code similar to the following.

MyClass object;

Multiple return values are handled as in the following snippet.

void multiReturn( MyClass *& object1, MyClass *& object2 )
{
    MyClassHandle obj1( new MyClass ), obj2( new MyClass );
    // Do some computations with obj1 and obj2
    object1 = HandleAsPointer( obj1 );
    object2 = HandleAsPointer( obj2 );
}

void callingFunction()
{
  MyClassHandle hObject1, hObject2;
  {
    MyClass *object1, *object2;

    hObject1 = MyClassHandle( object1 );
    hObject2 = MyClassHandle( object2 );
  }
This idiom is admittedly cumbersome. Fortunately, multiple return values are rare.

For whatever reason, some debuggers are unable to understand handles. Thus while

handle->inspect();
is understood by the C++ compiler, it must be called as follows in gdb.
(gdb) call handle.obj->inspect()
The total number of reference-counted objects in existence at any time is recorded in the variable
int IotrRefCount::instances;
Thus it is a good idea to have a test in your main routine to check for memory leaks.
int main()
{
   { // enclosed block
        // Code that may allocate reference-counted objects
   } // end enclosed block
   assert( 0 == IotrRefCount::instances );

   return 0;
}
Note that it is usually necessary to create a separate enclosed block to contain any variables bound to reference-counted objects, so that all handles will go out of scope.

Reference Counts Using Pointers

The reference count of objects may be managed by calling ReferenceCounting and ReferenceCounting on pointer variables. We advise against maintaining reference counts using pointers because

See also:
IotrRefCount

Generated on Wed Aug 27 10:03:42 2003 for iotr by doxygen1.2.18