Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Reference counted smart pointer

4.50/5 (2 votes)
4 Dec 2012CPOL8 min read 26.3K   264  
This article presents a solution to manage pointers in C++ in a similar way as COM.

Introduction

With technology like WinRT, Microsoft is putting C++ back on the scene. Every one knows that C++ as a native language produces a fairly optimized code that will give your applications a good performance if properly used. However one of the drawbacks of C++ is that you have to manage memory and resources of the system yourself. 

For a generation of developers who were breastfed with manage languages like Java, C#, VB.NET, or Python, managing memory by themselves may appear tricky or even has-been!

As WinRT is based on good old COM and that one of the principles of COM is reference counting, I have tried to bring the safety of reference counting of COM smart pointers to normal C++ class instances. This short article describes a reference counted smart pointer that uses some of the principles of a COM component. It doesn't pretend to be a perfect solution but it brings some instance management to the world of native programming and illustrates some of the principles of C++ development and memory management.

Remark: The given solution is for Visual Studio 2012 Premium. However the code for the smart pointer can be used with any other Visual Studio version and with Windows XP to Windows 8. Visual Studio 2012 has a new unit test framework for C++ code, which is why this solution needs this version.

Background

It is better to have a good understanding of what a pointer and a reference means in C++ as opposed to a reference in a managed language like C#, however the goal of this article is also to help developers who are not yet familiar with those concepts to understand them properly.

Other notions of C++ like operator overloading, usage of the different types of constructors like copy constructor, as well as a simple template class are used in this article. I will shortly explain their usage and when it might be a bit tricky.

A simple architecture

My original requirement is to provide an automatic mechanism to manage the memory of an object referenced by a pointer and also support a scenario where the pointer or reference is held by an object and the object is deleted while its pointer is used by this other object. If you don't support a counting mechanism, your program will crash as it would try to use a pointer to an object that has been already deleted.

I have already found some implementations of smart pointers with reference counting but they impose that you use the smart pointer class everywhere you would use a pointer, even in parameters. I wanted to use a solution similar to the smart pointer of COM where you can still use a normal pointer in the signature of the method when you pass this pointer, but where everywhere you use a pointer to your object you put it in a smart pointer class to manage the life of your reference.

I also tried to make it compatible with using the delete operator if you don't manage the pointer with the smart pointer class. 

There are two classes to implement this smart pointer. One is something similar to the IUnknown interface of COM but without the QueryInterface() method, and the other class is a template class very similar to the _com_ptr class of COM which is the smart pointer used to manage COM references.

To be managed by my smart pointer, a class must implement the abstract class IRefCount that defines the AddRef() and Release() methods. 

C++ does't support constraints for templates like C# so your template definition may compile until you really instantiate with a concrete class. In the case of _refcnt_ptr it expects the class used to implement the IRefCount interface. In this article I may refer as interface a class that has only pure virtual methods. Standard C++ doesn't have interface keywords like Java or C#. The interface keyword in C++/CX is an extension of the language specific to Microsoft and the support of WinRT in the language.

One of the paradigms of C++ is that it lets the developer completely define a new type with a class. This new type will then behave exactly like a native type of the compiler as long as every bit of it has been created properly. This is why in C++ you can overload each and every operator of a class, including the new and delete operators as well as the cast operators. Operators have a very precise behavior in C++ depending on if they apply to a pointer, a reference, or a value object. For example a simple line of code may automatically invoke operators and constructors without you realizing it.

The copy constructor in C++ is extremely important and most of the time your classes must implement it properly, as the compiler is going to invoke it implicitly and if you don't provide it, it will use a default bit to bit copy which in most cases won't work.

Class diagram

This simple class diagram shows the hierarchy of classes and the relations between them. The smart pointer class is a template class, I described the fact that the template type is a IRefCount by associating the _refcnt_ptr with the IRefCount interface.

Let's dig into the smart pointer class.

The smart pointer class

The implementation I give here can be used as a base to create a more complete smart pointer or maybe adapted to specific needs.

A smart pointer needs to behave like a regular pointer when  you use it so it needs to overload some useful operators.

I have overloaded the following ones:

  • -> so you can call the methods of the wrapped pointer as if you were using it directly
  • * so you can write *myPtr
  • = because the compiler may invoke it where you don't expect...
  • T* cast operator so you can automatically extract the underlying pointer. 

It is also important to give a proper copy constructor to your smart pointer as again the compiler is going to invoke it silently:

C++
template<class T> class _refcnt_ptr sealed : public _refcnt
{
private:
    IRefCount* m_ptr;    // The pointer to manage

public:
    _refcnt_ptr() : m_ptr(nullptr)        
    {
    }

    _refcnt_ptr(const _refcnt_ptr<T>& otherPtr) : m_ptr(otherPtr.m_ptr)
    {
        _AddRef();;
    }

    _refcnt_ptr(T* ptr)  : m_ptr(ptr)
    {
        _AddRef();
    }

    ~_refcnt_ptr()
    {
        _Release();
    }

    T* operator->()
    {
        return static_cast<T*>(m_ptr);
    }

    T& operator* ()
    {
        return  *static_cast<T*>(m_ptr);
    }

    operator T*()
    {
        return static_cast<T*>(m_ptr);
    }

    _refcnt_ptr<T>& operator=(const _refcnt_ptr<T>& otherPtr)
    {
        if (this != &otherPtr)
        {
            _Release();
            m_ptr = otherPtr.m_ptr;
            _AddRef();
        }

        return *this;
    }

private:
    void _AddRef()
    {
        if (m_ptr != nullptr)
        {
            m_ptr->AddRef(true);
        }
    }

    void _Release()
    {
        if (m_ptr != nullptr)
        {
            m_ptr->Release(true);
        }
    }
};

The complete code is given there but like I said it is a starting point to illustrate the concept and when using it in different real situations, it might need to be enhanced.

The wrapped pointer is of type IRefCount, this is not necessary as C++ templates are not type safe like in C# but it makes the code more readable and the compiler will complain if you try to use it with a non IRefCount class. You can write the same using T* in place of IRefCount*, the compiler will complain if your class doesn't have the AddRef() and Release() methods. 

I have sealed the class as it is a stand-alone class that you won't inherit.

This smart pointer needs a IRefCount based class to work with. Let's look at the code of this class.

C++
class REFCNTDECL CRefCount : public IRefCount
{
private:
    int refCount;        // The reference counter
    int refPtrCount;    // Number of _refcnt_ptr monitoring that class instance

protected:
    CRefCount();

public:
    void operator delete(void *ptr);

    virtual void AddRef(bool isSmartPtr = false);

    virtual void Release(bool isSmartPtr = false);

protected:
    virtual bool CanReleaseRef();
};

This class does a little bit more than counting the number of references of your object. It is also responsible to delete the object when there is no more reference used. As a class that is managed by the smart pointer must inherit from this class, when its method Release is called and the reference count is 0, the Release method behaves like a 'suicide' method as it deletes the object itself. 

The AddRed() and Release() methods have a parameter with a default value set to false. This parameter is used to tell the reference counter if it is used from a smart pointer. This information is used by the delete operator implementation and the CanReleaseRef method. 

It is useful for the following scenario:

C++
TEST_METHOD(TestPersonAndAddress)
{
    _refcnt_ptr<CPerson> person = new CPerson();

    person->SetName(T_OLIVIER);
    person->SetSurname(T_ROUIT);

    _refcnt_ptr<CAddress> address = new CAddress(T_STREET, T_ZIP, T_CITY);

    person->SetAddress(address);

    Assert::AreEqual(T_OLIVIER, person->GetName().c_str());
    Assert::AreEqual(T_ROUIT, person->GetSurname().c_str());
    Assert::AreEqual(T_STREET, person->GetAddress().GetStreet().c_str());
    Assert::AreEqual(T_ZIP, person->GetAddress().GetZipCode().c_str());
    Assert::AreEqual(T_CITY, person->GetAddress().GetCity().c_str());

    _refcnt_ptr<CAddress> refAddress = &person->GetAddress();

    delete person;    // manually delete the person

    std::string city = refAddress->GetCity();
}    // Compiler will try to delete the person again using the smart pointer

The pointer instance of CPerson is managed by the smart pointer, so when I call delete on person, it would delete the person and the CAddress object associated with it. However because in the CPerson class the CAddress instance is managed with a smart pointer the reference to CAddress I got from the CPerson instance is still valid and the program won't crash.

Another effect is as I have overloaded the delete operator and used a counter of smart pointers monitoring this reference, the delete call doesn't delete the CPerson instance, it lets the smart pointer do it when it is out of scope.

Previously I said that the CAddress would be deleted when calling delete person, but because delete is in fact not really deleting the person instance, the address reference you have is protected by two mechanisms.  

I only tested a few scenarios and I'm pretty sure that I must have missed some and that in a real application this smart pointer would have to be... much more smarter! 

Using the debugger on the above unit test method you can see how the overloaded operators and constructors are called by the compiler on a simple method call. For example put a break point in person->SetAddress(address) and put some break points in _refcnt_ptr code. You will be surprised how many lines of the code are invoked just for this simple call and the single line of code of that method!

Points of Interest

I have been working with COM and C++ for almost 10 years when COM was a popular technology and Java was just starting to be used. Then .NET came and COM was kind of put back to sleep. This was the era of managed languages where the runtime took care of all the memory hassles for you, well so you think. It's been now almost 10 years that I'm working mostly with managed languages like C# and they definitely bring some more safety in the memory management, but it brings out other problems that are sometimes more difficult to solve because you don't any more get a fine control on what you are doing.  

So if you have to write WinRT components in C++, the C++/CX compiler is going to take care of some of the plumbing of COM that was troublesome in pure C++, but when it comes to your own C++ classes, you'll have to manage the memory and the life of your objects by yourself.

License

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