Counted Pointers

Counted pointers are used to share implementations of two or more identical objects. If an object is copied by value, then there is no reason to allocate memory for the second object (the copy), until and unless one of the objects changes its state.

Thus, if I have a string A, which says "Hello world!" and I copy it to string B, I can save memory by keeping only one copy of the string in memory. On the other hand, if I change string B to say "Goodbye world!" then I'd better make sure I have two different strings, or I'll inadvertently change string A, thus violating the semantics of copy by value.

Counted pointers also deal with the situation of two objects pointing to the same third object. It is possible in this situation for incorrectly written code to destroy the object twice (an error) or not at all (a memory leak), or even for one object to be left with a pointer to memory which is no longer valid (a stray pointer). Counted pointers eliminate these possibilities.

Several counted pointer implementations have been discussed in the literature and on the news groups. They all revolve around two ideas. The first idea is to use a counter to keep track of how many other objects are referencing the target object. The second is to define a second class, often called the handle, and to structure the code so that only handles may reference the target object.

The proposed programming idioms vary significantly. Some authors put the counter in the target class; others assume that the target class is predefined, perhaps in a library for which you do not have the source code. In this case, they wrap the target class with another class: the handle. The handle holds the counter and provides pointer-like access to the object. Some approaches implement the handle as a template. This requires the handle class to be a friend of the target class, or otherwise able to access the counter.

The C++ code below shows an implementation of these ideas. In this case, we assume that the Handle and the Target are designed together. A pointer to the Target class, pTarget, is put in the Handle. The counter is put in the Target class and the Handle made a friend of the Target class. Note that the constructor of the Target is made private. This is done so that only a Handle object (which is a friend) can instantiate a Target object. The constructor of the Handle class sets the reference counter to 1 and the pointer, pTarget, to refer to the Target object. The copy constructor and assignment operator copy the pointer and increment the counter. The de-referencing operators return the pointer to the Target, thereby delegating all member function calls to the Target object.

#include <iostream.h>

class Foo
{
   //...
};

class Handle;

class Target 
{
public:
   friend class Handle;
   // real member functions go here
   // ...
private:
   int counter;
   Target(Foo* foo) {}
   ~Target() {}
};

class Handle 
{
public:
   Handle(Foo* foo) 
   { 
      pTarget = new Target(foo); 
      pTarget->counter=1;
   }
   
   Handle(const Handle& rhs) 
   { 
      pTarget = rhs.pTarget; pTarget->counter++; 
   }
   
   ~Handle() 
   { 
      if ((--(pTarget->counter)) <= 0) delete pTarget; 
   }

   Handle& operator=(const Handle& rhs) 
   { 
      (rhs.pTarget->counter)++;
      if ((--(pTarget->counter)) <= 0 ) 
         delete pTarget;
      pTarget = rhs.pTarget;
      return *this;
   }

   Target* operator->() 
   { 
      return pTarget; 
   }

private:
   Target* pTarget;
};

© 1998 by Wrox Press. All rights reserved.