How to Implement Smart Pointer in C++?

What is Smart Pointer in C++?

From the name we can imagine that there is something smart about these smart pointers over normal ones. When we talk about pointers we essentially talk about memory operation and management. In C/C++, programming memory management is always a programmer’s nightmare. Not because it’s very difficult but the programmers make unintentional mistakes that eventually lead to memory corruption and leak.

The smart pointers come to the rescue, at least to a great extent. We can use smart pointers and share them with multiple functions and even threads without worrying about where or when to free them. So, we can avoid memory leak – not freeing at all, or multiple frees of the same pointer – memory violation.

In short, the smart pointers can manage the dynamically allocated object’s life cycle on its own in a very elegant way.

But the reality is that the smart pointers are not technically pointers – they look like pointers but they are actually C++ objects that manage other dynamic objects through pointers. The usage of smart pointer is just like a normal pointer – we can do de-referencing (operator *) and indirection (operator ->) on them.

Problem with Normal Pointers

To understand the smart pointers, let’s start with the problems we face with the normal pointer. Then we’ll see how smart pointers help solve them.

Consider this code snippet.

#include <string.h>
#include <iostream>
using namespace std;

class Animal
{
private:
    char * m_name;
public:
    Animal(char *name)
    {
        if(name == NULL)
        {
            m_name = NULL;
        }
        else
        {
            m_name = new char[strlen(name)];
            strcpy(m_name, name);
            cout << "Memory is allocated in constructor." << endl;
        }
    }

    ~Animal()
    {
        if(m_name)
        {
            delete [] m_name;
            cout << "Memory is cleaned up in destructor." << endl;
        }
    }

    void DisplayName()
    {
        cout << "Name: " << m_name << endl; 
    }
}; 

int main() {
    Animal * cat = new Animal("Cat");
    cat->DisplayName();

    delete cat;

    return 0;
}

Memory is allocated in constructor.
Name: Cat
Memory is cleaned up in destructor.

When we created the object, cat, some memory was allocated in the constructor to hold its name which got freed from the destructor when we deleted the object. So in this case there was no memory leak.

Memory leak will occur if we forget to delete the object like this main function.

int main()
{
    Animal * cat = new Animal("Cat");

    cat->DisplayName();

    return 0;
}
Memory is allocated in constructor.
Name: Cat

From this output, we can see that the destructor is not called which means allocated memory is not freed. There will be a memory leak. As this example program will terminate soon, there will be no real impact of this leak. But for a long running program, continuous memory leak will lead to shortage of memory and eventual program termination.

It is not only about forgetting to delete the object. Sometimes deleting the object is inconvenient also.

Consider this pseudo code.

int some_function()
{
    Animal * a = new Animal("Cat");

    do something...

    if (some_condition == false)
    {
        delete a;
        return 0;
    }

    if (some_condition == true)
    {
        do something...
        if (some_condition == false)
        {
            delete a;
            return 0;
        }
    }
    delete a;
    return 1;
}

In this example, we have 3 return points. To delete the allocated object properly, we need to delete it from all exit points. For complicated programs with a pointer being used in various branches, functions or even threads, there will be a very high chance to miss from some place when we shouldn’t. At the same time there will be a chance to delete the same object more than once or use after delete which will lead to memory violation.

But with smart pointers, we don’t need to worry about all these things. We can use a smart pointer in any way. The cleanup will happen automatically when the object will no longer be required – all the smart pointers are deleted or went out of context. A big relief for the developer.

Smart Pointer Implementation

Implementing smart pointer means defining a class that will contain a pointer of the managed object. We should be able to do dereferencing (operator *) and indirection (operator ->) on the actual object pointer using a smart pointer object. This is possible if we overload these operators (* and ->).

class SmartPointer
{
private:
    Animal*    m_data;
public:
    SmartPointer(Animal* pValue) : m_data(pValue)
    {
    }
    ~SmartPointer()
    {
        delete m_data;
    }

    Animal& operator* ()
    {
        return *m_data;
    }

    Animal* operator-> ()
    {
        return m_data;
    }
};

Now we can change our main() function like this.

int main()
{
    SmartPointer cat(new Animal("Cat"));

    cat->DisplayName();

    return 0;
}

Output of this program is like this.

Memory is allocated in constructor.
Name: Cat
Memory cleaned up in destructor.

Here we did not delete the object explicitly. But from the output we can see that destructor gets called. And the allocated memory is freed.

Better Way of Implementation

The above SmartPoiner class is implementation in such a way that it will work only for Animal class. It is not practical to define a different smart pointer class for every class we want to use. Rather we can templatised the SmartPointer class such that it works for any class.

template <class t>
class SmartPointer
{
private:
    T*    m_data;
public:
    SmartPointer(T* pValue) : m_data(pValue)
    {
    }
    ~SmartPointer()
    {
        delete m_data;
    }

    T& operator* ()
    {
        return *m_data;
    }

    T* operator-> ()
    {
        return m_data;
    }
};

We have to change our main() function a little bit.

int main()
{
    SmartPointer<Animal> cat(new Animal("Cat"));

    cat->DisplayName();

    return 0;
}

At the End

This article is intended to understand the concept of smart pointers. But this implementation is not suitable for many practical cases. If we assign a smart pointer to another one or if you pass to other functions or threads, lot of complicacies will arise. To tackle those scenarios, c++11 introduced different types of smart pointers like unique pointer and shared pointer.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

3 thoughts on “How to Implement Smart Pointer in C++?”

  1. I’ve searched through the whole web, nothing like this article!
    Amazing! makes everything so clear and simple
    Thank you so much

Leave a Reply

Your email address will not be published. Required fields are marked *

0
3
0
2
2
0