Templates and Smart Pointers

OverviewHow Do I

C++ allows you to create “smart pointer” classes that encapsulate pointers and override pointer operators to add new functionality to pointer operations. Templates allow you to create generic wrappers to encapsulate pointers of almost any type.

The following code outlines a simple reference count garbage collector. The template class Ptr<T> implements a garbage collecting pointer to any class derived from RefCount.

#include <stdio.h>

#define TRACE printf

class RefCount {
    int crefs;
public:
    RefCount(void) { crefs = 0; }
    ~RefCount() { TRACE("goodbye(%d)\n", crefs); }
    void upcount(void) { ++crefs; TRACE("up to %d\n", crefs);}
    void downcount(void)
     {
     if (--crefs == 0)
      {
      delete this;
      }
     else
      TRACE("downto %d\n", crefs);
     }
};

class Sample : public RefCount {
public:
    void doSomething(void) { TRACE("Did something\n");}
};

template <class T> class Ptr {
    T* p;
public:
    Ptr(T* p_) : p(p_) { p->upcount(); }
    ~Ptr(void) { p->downcount(); }
    operator T*(void) { return p; }
    T& operator*(void) { return *p; }
    T* operator->(void) { return p; }
    Ptr& operator=(Ptr<T> &p_)
        {return operator=((T *) p_);}
    Ptr& operator=(T* p_) {
        p->downcount(); p = p_; p->upcount(); return *this;
    }
};

int main() {
    Ptr<Sample> p  = new Sample; // sample #1
    Ptr<Sample> p2 = new Sample; // sample #2
    p = p2; // #1 will have 0 crefs, so it is destroyed;
            // #2 will have 2 crefs.
    p->doSomething();
    return 0;
    // As p2 and p go out of scope, their destructors call
    // downcount. The cref variable of #2 goes to 0, so #2 is
    // destroyed
}

Classes RefCount and Ptr<T> together provide a simple garbage collection solution for any class that can afford the int per instance overhead to inherit from RefCount. Note that the primary benefit of using a parametric class like Ptr<T> instead of a more generic class like Ptr is that the former is completely type-safe. The preceding code ensures that a Ptr<T> can be used almost anywhere a T* is used; in contrast, a generic Ptr would only provide implicit conversions to void*.

For example, consider a class used to create and manipulate garbage collected files, symbols, strings, and so forth. From the class template Ptr<T>, the compiler will create template classes Ptr<File>, Ptr<Symbol>, Ptr<String>, and so on, and their member functions: Ptr<File>::~Ptr(), Ptr<File>::operator File*(), Ptr<String>::~Ptr(), Ptr<String>::operator String*(), and so on.