Reference Counting Class Objects in C++

Posted in software by Christopher R. Wirz on Wed Jul 04 2018



In C++ there are two main ways of passing a class object into a function for modification - by value and by reference.


MyClass PassByValue(MyClass m) { 
	
	// An automatic copy of m comes in to the method
	m.Count++; // The copy is modified
	
	// The copy of m is automatically copied and returned 
	// if return value optimization is not turned on
	return m; // otherwise m is returned	
}

void PassByReference(MyClass& m) {
	// m is modified, nothing is returned
	// no copies made
	m.Count++;
}

The only exception to this rule is arrays (and array-like types such as vectors), which are always passed by reference. This is because the array is really a wrapper around a pointer to a memory allocation (as observed through application telemetry). (Think about it: you can allocate an array far greater than the stack size and do not get any exception.)

As a side note: If you don't want to copy your input, but want to still use it, you can apply const to the method signature.

Here's an example:


int GetCountByReference(const MyClass& m) {
	// m is NOT modified OR copied
	return m.get_Count(); // a copy of count is returned
}

The last way to copy by value using the assignment operator.

Here's an example:


void MyClassWrapper::SetMemberValue(MyClass& m) {
	this->_member = m;
}

This is by far the most interesting case, because the original _member loses scope and so its destructor is called. Meanwhile, the value of _memeber is assigned to m. The copy constructor is not called, but the assignment operator is. If m were to lose scope outside of this assignment method, the value of m would be destroyed and the wrapper class would have a useless _member (this will cause an exception if, for example, the MyClassWrapper index [] operator is called).

If only there were some way to allow MyClass objects to get passed around without having to worry about losing scope or reference. This is the intuition of making a class self-reference-counting (keep in mind, this is not how iOS performs reference counting - they use a mix of pooling mechanisms).

Imagine the following example (based on the above cases):


// MyClass.hpp
#ifndef MY_CLASS_HPP
#define MY_CLASS_HPP
#include <stdio.h>
#include <stdlib.h>
#include "ReferenceCounter.hpp"

template<typename T>
class MyClass : ReferenceCounter {
	public:
		MyClass(unsigned int count) {
			if (count > 0) {this->_count = count;}			
			this->_allocation = (T*)malloc(sizeof(T) * this->_count);
		}
		MyClass() : MyClass(128) {}
		
		T& operator [](const unsigned int index){
			return this->_allocation[index % this->get_Count()];
		}
		
		int get_Count() const { return this->_count; }
		
		virtual ~MyClass();
		
	private:
		unsigned int _count = 128; // stack allocated
		T* _allocation; // memory allocated
};

// Just to show a definition outside of the declaration
// This could be put in a MyClass.cpp file if we bothered to make one
template<typename T>
MyClass<T>::~MyClass() {
	if (this->CanDispose()) {
		// clean up the resources only if it should be cleaned up
		free(this->_allocation);
		this->_allocation = NULL;
		this->_count = 0;
	}
}

// A type definition can be created from a template class with a using statement
using MyDoubleClass = MyClass<double>;

class MyWrapperClass {
	public:
		MyWrapperClass(unsigned int count) {
			this->_member = MyDoubleClass(count);
		}
		
		void SetMemberValue(MyDoubleClass& m) {
			this->_member = m;
		}
		
		double& operator [](const unsigned int index){return this->_member[index];}
		
		int get_Count() const { return _member->get_Count(); }

	private:
		MyDoubleClass _member = MyDoubleClass(); // stack allocated default value
};
#endif

How do we create a ReferenceCounter base class that will ensure

First create the declarations file


// ReferenceCounter.hpp
#ifndef REFERENCE_COUNTER_HPP
#define REFERENCE_COUNTER_HPP
class ReferenceCounter {
	public:
		// Parameterless Constructor
		// (called by default)
		ReferenceCounter();
		
		// Copy Constructor
		ReferenceCounter(const ReferenceCounter &r);
		
		// Assignment Operator
		ReferenceCounter& operator=(const ReferenceCounter &r);		
		
		// Destructor
		virtual ~ReferenceCounter();
		
	protected:
		// true if resources can be freed (const safe)
		bool CanDispose() const;
		
	private:
		// A pointer to 
		// How many objects hold reference to this object
		int* _references;
};
#endif

Then create the definitions file


// ReferenceCounter.cpp
#include <stdio.h>
#include <stdlib.h>
#include "ReferenceCounter.hpp"

ReferenceCounter::ReferenceCounter()
{
	// Allocate an integer in memory to keep track of references
	this->_references = (int*)malloc(sizeof(int));
	this->_references[0] = 1;
}

ReferenceCounter::ReferenceCounter(const ReferenceCounter& r)
{
	// Inherit and increment the reference count
	if (r._references != this->_references) {
		this->_references = r._references;
		this->_references[0]++;
	}
}

ReferenceCounter & ReferenceCounter::operator=(const ReferenceCounter & r)
{
	// Inherit and increment the reference count
	if (r._references != this->_references) {
		this->_references = r._references;
		this->_references[0]++;
	}
	return *this;
}

bool ReferenceCounter::CanDispose() const
{
	// Since the ReferenceCounter is the base class, its destructor is called last
	// thus, _references is <= 1, not <=0.
	return this->_references == NULL || this->_references[0] <= 1;
}

ReferenceCounter::~ReferenceCounter()
{
	// Reduce the number of reference by one
	this->_references[0]--;
	
	// If there are no more reference, clean up the memory
	if (this->_references[0]<=0) {
		free(this->_references);
		this->_references = NULL;
	}
}

Give it a try. You will find that the stack cleans up your class object right after you are done using it. No need to use the new or delete key word. No need to perform your own malloc or free.