Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C++

Building a Quick and Handy Reference Counting Class

Rate me:
Please Sign up or sign in to vote.
4.10/5 (13 votes)
15 Mar 2010CPOL4 min read 50.4K   238   22   6
The article describes how to implement reference counting in a C++ class that can be used like any pointer

Introduction

Although C++ is an excellent choice to combine a modular object-oriented design with high speed and control, it usually gets tedious when it comes to memory management. In complex systems, extreme care has to be taken to avoid both memory leaks and attempts to access unallocated memory. Even simple memory management techniques can be of great help in this case. In this article, I describe reference counting, one of the simplest memory management techniques that is sufficient for many cases. I will explain how it can be implemented in C++ and how to wrap it in a simple pointer class that "does the job" of enabling a managed-alike programming style without too much coding hassle or need for a 3rd party library.

Background

The concept of reference counting is simple; each object maintains a reference counter (RC) that holds the number of pointers pointing to that object. This counter is updated whenever a new pointer points to the object or a pointer pointing to the object is changed. When this counter reaches 0, it indicates that the object is no longer accessible by the program and hence should be deallocated.

rcptr01.png

Reference counting will do fine as long as you avoid reference cycles. If reference counting managed objects point to each other in a cycle, you will end up with a cluster of objects that are not accessible by the program but having their reference counts higher than 0, thus causing a memory leak.

rcptr02.png

The straightforward solution to reference cycles is to classify pointers into strong pointers and weak pointers. Weak pointers do not affect the reference counters of the objects they point to. You should be careful in your design to ensure that no cycle of strong pointers can be formed.

rcptr03.png

Implementing Reference Counting

To implement reference counting in C++, we need to define a class that maintains a reference counter, supports incrementing and decrementing that counter and destroys and deallocates itself when its counter reaches 0.

C++
/**
* Base class for all classes that support reference counting
*/
class RCBase
{
public:
	RCBase() : mRefCount(0) {}
	virtual ~RCBase() {}

	void grab() const {++mRefCount;}
	
	void release() const
	{
		assert(mRefCount > 0);
		--mRefCount;

		if(mRefCount == 0) {delete (RCBase *)this;}
	}

private:
	mutable int mRefCount;
};

Notice that I declared mRefCount as mutable so that it can be changed by the const methods grab and release, allowing reference counting to operate even on pointers to constant objects. All classes for which we want to use reference counting must be derived, either directly or indirectly, from RCObject. Usage of this class is simple; to maintain a strong pointer on an object of class MyClass, simply point to it by a MyClass* and invoke grab. Once you are done with that object, invoke release. The object will delete itself if it is no longer pointed to somewhere else. Notice that the reference counter is initialized to 0 which means that when you create a new object, you have also to grab it. This will be convenient when we develop our pointer. The following code example demonstrates RCBase:

C++
class MyClass : public RCBase
{
public:
	~MyClass() {cout << this << " is no longer needed" << endl;}
	void print() {cout << "Hello" << endl;}
};

int main(void)
{
	//1: Demonstrate RCBase class

	//Module 1 creates an object
	MyClass *a = new MyClass();
	a->grab();		//RC=1

	//Module 2 grabs object
    MyClass* ptr = a;
    ptr->grab();    		//RC=2

    //Module 1 no longer needs object
    a->release();      		//RC=1
    a = NULL;

    //Module 2 no longer needs object
    ptr->release();    		//object will be destroyed here
    ptr = NULL;
}

Usually, an object will be grabbed in the beginning of a function and released at the end of it, or it will be grabbed in the constructor of the pointing object and released in its destructor. You will find base classes similar to RCbase in frameworks that use reference counting such as Microsoft COM and Irrlicht engine. Although the framework established so far simplifies memory management, there are still two problems:

  • You might simply forget to grab or release an object.
  • If an object is grabbed in a function, it might not be released if, for example, the function is prematurely terminated due to an exception. We thus need a framework that "automatically" grabs and releases objects as needed and also releases all objects grabbed in a function whenever it terminates.

We can adapt the auto_ptr class provided by standard C++ library. auto_ptr is a strong pointer that, whenever changed or destroyed, destroys the object it was pointing to. It is thus useful when it is guaranteed that no object will have more than one strong pointer pointing to it simultaneously. All we need to do is to relax auto_ptr class such that when it is changed, it releases the object it was pointing to and grabs the object it is going to point to. This results in a simple template class:

C++
/**
* A reference counting-managed pointer for classes derived from RCBase which can
* be used as C pointer
*/
template < class T >
class RCPtr
{
public:
	//Construct using a C pointer
	//e.g. RCPtr< T > x = new T();
	RCPtr(T* ptr = NULL)
		: mPtr(ptr)
	{
		if(ptr != NULL) {ptr->grab();}
	}

	//Copy constructor
	RCPtr(const RCPtr &ptr)
		: mPtr(ptr.mPtr)
	{
		if(mPtr != NULL) {mPtr->grab();}
	}

	~RCPtr()
	{
		if(mPtr != NULL) {mPtr->release();}
	}

	//Assign a pointer
	//e.g. x = new T();
	RCPtr &operator=(T* ptr)
	{
		//The following grab and release operations have to be performed
		//in that order to handle the case where ptr == mPtr
		//(See comment below by David Garlisch)
		if(ptr != NULL) {ptr->grab();}
		if(mPtr != NULL) {mPtr->release();}
		mPtr = ptr;
		return (*this);
	}

	//Assign another RCPtr
	RCPtr &operator=(const RCPtr &ptr)
	{
		return (*this) = ptr.mPtr;
	}

	//Retrieve actual pointer
	T* get() const
	{
		return mPtr;
	}

	//Some overloaded operators to facilitate dealing with an RCPtr 
         //as a conventional C pointer.
	//Without these operators, one can still use the less transparent 
         //get() method to access the pointer.
	T* operator->() const {return mPtr;}		//x->member
	T &operator*() const {return *mPtr;}		//*x, (*x).member
	operator T*() const {return mPtr;}		//T* y = x;
	operator bool() const {return mPtr != NULL;}	//if(x) {/*x is not NULL*/}
	bool operator==(const RCPtr &ptr) {return mPtr == ptr.mPtr;}
	bool operator==(const T *ptr) {return mPtr == ptr;}

private:
	T *mPtr;	//Actual pointer
};

Our new class can now be used as follows:

C++
//Module 1 creates an object
RCPtr< MyClass > a2 = new MyClass();	//RC=1
	
//Module 2 grabs object
RCPtr< MyClass > ptr2 = a2;		//RC=2
    
//Module 2 invokes a method
ptr2->print();
(*ptr2).print();

//Module 1 no longer needs object
a2 = NULL;      			//RC=1
    
//Module 2 no longer needs object
ptr2 = NULL;    			//object will be destroyed here

Now, you no longer have to worry about grabbing and releasing objects. Even if the lines a2 = NULL and ptr2 = NULL were not present, the object will be correctly destroyed at the end of the scope of a2 and ptr2. Your only concern should be when to use a strong pointer (RCPtr) and when to use a weak pointer (traditional C pointer).

History

  • 03/09/2010 - Initial submission
  • 03/16/2010 - Fixed a bug that caused access violation when assigning a pointer to a RCPtr already pointing to the same object

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Instructor / Trainer Cairo University
Egypt Egypt
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralConcepts for reference counting Pin
Stefan_Lang15-Mar-10 23:23
Stefan_Lang15-Mar-10 23:23 
In your introduction you imply that in an implementation for reference counting the object always holds the reference counter (in your example by inheritance). This is but one possible implementation of a reference counting concept however.

A reference counter does not need to be part of an object (or a base class), and the object doesn't even need to know about it for all it cares. The solution you provide does that. This is an approach called 'intrusive reference counting' because you need to 'intrude' on each type of object definition that you want reference counting implemented for. It is sensible to do this for an OO design when the object itself 'knows' that the added storage and function call levels implied cost less than the off-chance of someone accidentally forgetting to release memory, or if you don't simply care about performance cost. If you build an application from scratch, it is perfectly reasonable to do this.

Other implementations of reference counting create specialized types of pointers ('smart pointers') that collectively own the reference counter object (usually just a standalone int), and destroy that object upon decrementation to 0. This is preferable in old code that you do not want to touch or when you are referencing third-party objects whose class declarations you cannot change to inherit from some base class. boost:shared_ptr implements this concept, and you can also find an almost complete example in Bjarne Stroustrup's "The C++ Programming Language". The disadvantage of this concept is that each smart pointer needs to hold two pointers - one to the object they point to and one to the collectively shared reference counter. It is also neccessary for the application that use these smart pointers to be aware of that fact, since they explicitely need to use the appropriate smart pointer types and may never call delete on them (which is about the only thing that ultimately distinguishes them from 'normal' pointers). Note that the auto_ptr type is a rather incomplete implementation of smart pointers.

Yet another approach is that you implement a memory manager that holds reference counters. I consider this the 'most object-oriented' approach because reference counting is really the job of a memory manager. It does afford having your own memory manager though, and most applications don't bother with that.

I did implement this last concept in my own Pool memory manager: in this implementation the memory block object also holds a corresponding block of 'use counters' (which are in fact reference counters), so there is no need to individually destroy reference counter objects as they're being destroyed along with the blocks that hold the memory for the objects being managed by the pool. The memory manager also returns it's own specialized smart pointers.

Just wanting to give you some insight on various possible solutions. There are more, and the boost library offers quite a few variants if you're interested.
Questionpossible bug? Pin
david garlisch15-Mar-10 14:10
david garlisch15-Mar-10 14:10 
AnswerRe: possible bug? Pin
Ahmed S. Hefny15-Mar-10 18:34
Ahmed S. Hefny15-Mar-10 18:34 
GeneralRe: possible bug? Pin
david garlisch15-Mar-10 18:45
david garlisch15-Mar-10 18:45 
GeneralIncrement/Decrement Pin
basementman10-Mar-10 3:57
basementman10-Mar-10 3:57 
GeneralRe: Increment/Decrement Pin
Ahmed S. Hefny11-Mar-10 8:39
Ahmed S. Hefny11-Mar-10 8:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.