Creating smart pointers in C++

Introduction

What are smart pointers? They are a means of handling the problems associated with normal pointers, namely memory management issues like memory leaks, double-deletions, dangling pointers etc. This post gives a simple guide to creating your own smart pointer in C++. As a simple starting example, consider a basic template class which can be used to hold generic data types:

template <class T>
class Ptr
{
public:
	Ptr(T* d) { data = d;}	 

private:
	T* data;
};

And also consider an example class A which we will use the the smart pointer to hold:

class A
{
public:
	A() {}
	~A() {}
	void DoStuff() { std::cout << "Hello"; }
};

One often-encountered problem is that of forgetting to delete. Or maybe some exception gets thrown and the function is never given the chance to delete. Either way, the result is a memory leak, as would be the case in the following function since the pointer is never deleted:

void function()
{	
	Ptr<A> p( new A() );		
	// memory leak!
}

Note that each time function() is called, memory gets allocated for Ptr but is never deleted when the function goes out of scope. This kind of memory leak is precisely the kind of thing we wish to avoid. We need some mechanism that takes care of the task of deleting the pointer for us, lest we forget.

Given that pointers do not have destructors, we can make our smart pointer class ‘smart’ by giving it one. Our Ptr class will not only hold the pointer, but delete it when the destructor is called. We no longer have to worry about deleting Ptr when it goes out of scope:

template <class T>
class Ptr
{
public:
	Ptr(T* d) { data = d;}	
	~Ptr() { delete data; }  

private:
	T* data;
};

Try running function again, to demonstrate that this actually happens. (You may wish to insert a breakpoint in the Ptr destructor to verify this.)

void function()
{	
	Ptr<A> p( new A() );		
	// pointer deleted when we go out of scope...
}

Creating the interface for the smart pointer

Since a smart pointer is meant to look like and behave like a smart pointer without actually being a pointer, it should support the same kinds of interfaces as pointers such as the dereferencing (‘*‘) and indirection (‘->‘) operators. This is what the updated Ptr class looks like with the overloaded ‘*‘ and ‘->‘ operators added:

template <class T>
class Ptr
{
public:
	Ptr(T* d) { data = d;}	
	~Ptr() { delete data; }	

	T* operator->() { return data; }
	T& operator*() { return *data; }

private:
	T* data;
};

For example, we can now use the overloaded ‘->‘ (indirection) operator to access the DoStuff method in class A, so that it now looks like a standard pointer operation (without actually being one):

void function()
{	
	Ptr<A> p( new A() );
	p->DoStuff();	
	// pointer deleted when we go out of scope...
}

Using Reference Counting

There is however, one as yet unanticipated danger of deleting the pointer more than once. Try the following code segment to see what I mean:

void function()
{	
	Ptr<A> p( new A() );
	p->DoStuff();	

	Ptr<A> q = p;
	// Danger! pointer double-deleted...
}

In here p is assigned to q, and both refer to the same class A pointer. As soon as we go out of scope the destructor of Ptr is called twice, the first of which is successful, while the second fails, since p and q are referring to the same pointer. The same thing would happen even if we were using normal pointers.

The way around this is to use reference counters to track of the pointers we have added or deleted, and add it to our smart pointer class. We maintain a pointer to the reference counter class in our smart pointer class and this pointer is shared for all instances of the smart pointer that refers to the same pointer. To implement this we must also maintain an assignment operator and copy constructor in our smart pointer class:

class ReferenceCounter
{
private:
	int count; 

public:
    void Add() { count++; }

	// Decrement and release reference count
    int Release() { return --count; }
};

template <class T>
class Ptr
{
public:

	Ptr() : data( 0 ), ref( 0 ) 
	{		
        ref = new ReferenceCounter();        
        ref->Add();
	}

	Ptr(T* d) : data(d), ref(0)
	{ 
		ref = new ReferenceCounter();        
        ref->Add();
	}		

	// Copy constructor
    Ptr(const Ptr<T>& ptr) : data(ptr.data), ref(ptr.ref)
    {
        // Copy constructor
        // Copy the data and reference pointer
        // and increment the reference count
        ref->Add();
    }

	// Assignment operator
	Ptr<T>& operator = (const Ptr<T>& ptr)
    {
        // Assignment operator
        if (this != &ptr) // Avoid self assignment
        {
            // Decrement the old reference count
            // if reference become zero delete the old data
            if(ref->Release() == 0)
            {
                delete data;
                delete ref;
            }

            // Copy the data and reference pointer
            // and increment the reference count
            data = ptr.data;
            ref = ptr.ref;
            ref->Add();
        }
        return *this;
    }

	~Ptr()
	{ 
		// Only when ref. count reaches 0 delete data
		if(ref->Release() == 0)
        {
            delete data;
            delete ref;
        }
	}	

	T* operator->() { return data; }
	T& operator*() { return *data; }

private:
	T* data;
	ReferenceCounter* ref;
};

Now lets try it in a situation whereby the destructor is called multiple times:

void function()
{	
	Ptr<A> p( new A() );
	p->DoStuff();
	{
		Ptr<A> q = p;
		q->DoStuff();
		// Destructor of q called here..

		Ptr<A> r;
        r = p;
        r->DoStuff();
        // Destructor of r called here..
	}
	p->DoStuff();
    // Destructor of p will be called here 
}

This is what happens step-by-step: we create a smart pointer p of type A. The constructor of Ptr is called, the data is stored, and a new ReferenceCounter pointer is created . The Add method of ReferenceCounter increments the reference count from 0 to 1.

The line Ptr q = p; creates a new smart pointer q using the copy constructor and the data is copied and the reference further incremented to 2 .

The line r = p; assigns the value of p to q using the asignment operator. The data is also copied and the reference count further incremented to 3. When r and q go out of scope, (when we leave the inner curly brackets) the destructors of the respective objects are called.

The reference count is decremented, but the data is not deleted until the reference count is zero. This happens only when the destructor of p is called. Hence our data is deleted only when nothing is referring to it.

Here is the complete code listing:

#include <iostream>

class A
{
public:
	A() {}
	~A() {}
	void DoStuff() { std::cout << "Hello"; }
};

class ReferenceCounter
{
private:
	int count; 

public:
    void Add() 
	{ 
		count++; 
	}

	// Decrement and release reference count
    int Release()
	{ 
		return --count; 
	}
};

template <class T>
class Ptr
{
public:

	Ptr() : data( 0 ), ref( 0 ) 
	{		
        ref = new ReferenceCounter();        
        ref->Add();
	}

	Ptr(T* d) : data(d), ref(0)
	{ 
		ref = new ReferenceCounter();        
        ref->Add();
	}		

	// Copy constructor
    Ptr(const Ptr<T>& ptr) : data(ptr.data), ref(ptr.ref)
    {
        // Copy constructor
        // Copy the data and reference pointer
        // and increment the reference count
        ref->Add();
    }

	// Assignment operator
	Ptr<T>& operator = (const Ptr<T>& ptr)
    {
        // Assignment operator
        if (this != &ptr) // Avoid self assignment
        {
            // Decrement the old reference count
            // if reference become zero delete the old data
            if(ref->Release() == 0)
            {
                delete data;
                delete ref;
            }

            // Copy the data and reference pointer
            // and increment the reference count
            data = ptr.data;
            ref = ptr.ref;
            ref->Add();
        }
        return *this;
    }

	~Ptr()
	{ 
		// Only when ref. count reaches 0 delete data
		if(ref->Release() == 0)
        {
            delete data;
            delete ref;
        }
	}	

	T* operator->() { return data; }
	T& operator*() { return *data; }

private:
	T* data;
	ReferenceCounter* ref;
};

void function()
{	
	Ptr<A> p( new A() );
	p->DoStuff();
	{
		Ptr<A> q = p;
		q->DoStuff();
		// Destructor of q called here..

		Ptr<A> r;
        r = p;
        r->DoStuff();
        // Destructor of r called here..
	}
	p->DoStuff();
    // Destructor of p will be called here 
}

int main()
{
	function();	
	return 0;
}

Latest Comments

  1. Dimitris Menounos 7 August 2012
    • Andy 7 August 2012

Leave a Reply to Dimitris Menounos Cancel reply