What are smart pointers and when should I use one?

In this tutorial, you will learn smart pointers and why and how to use the smart pointer in C++ programs. We will see first what are smart pointers and when we should use them. The primary pre-requisite of this tutorial is that you should have basic knowledge about pointers. Before understanding the application of smart pointers let’s understand the issue with normal pointers.

 

What are the issues with normal or raw pointers?

I believe you know that the memory allocated by new will not destroy automatically, you have to do it manually by calling the delete. It offers you the advantages of keeping them as long as you want.

The issue with ‘raw’ C++ pointers is that the programmer has to explicitly destroy the object when it is no longer useful. If you forgot to release the allocated memory or an exception occurs before deleting the memory, the memory leaks will occur. As all of you know, a memory leak occurs when programmers forget to deallocate the allocated memory.

See the below C++ program,

#include <iostream>
using namespace std;


void fun()
{
    // Using a raw pointer -- not recommended.
    int* ptr = new int;

    /*
    Use ptr...
    */
}

int main()
{
    // Infinite Loop
    while (1)
    {
        fun();
    }

    return 0;
}

The above-mentioned function fun() is creating a local raw pointer that is pointing to the allocated memory for the integer. When the function fun() ends, the local pointer ptr will be destroyed as it is a stack variable. But, the memory which it is pointing to won’t be deallocated because we forgot to use delete ptr; at the end of the fun(). So the memory gets leaked because the allocated memory becomes unreachable and cannot be deallocated.

 

But now you will say it is a programmer mistake I will never forget to add delete. I always write clean and error-proof code, why should I use smart pointers? And you asked me, “Hey, check my code”, here I am allocating the memory and deallocating it properly after its uses. Now tell me “Why I should use a smart pointer and what is the need for a smart pointer”?

#include <iostream>
using namespace std;


void fun()
{
    // Using a raw pointer -- not recommended.
    int* ptr = new int;

    /*
    Use ptr...
    .
    .
    .
    */
    delete ptr;
}

int main()
{
    // Infinite Loop
    while (1)
    {
        fun();
    }

    return 0;
}

After looking at your code I agree with your words that you are allocating and releasing the memory properly. Also, your code will work perfectly in normal scenarios.

But think about some practical scenarios. There might be the possibility that some exception occurs due to some invalid operation between the memory allocation and deallocation. This exception could be due to accessing an invalid memory location, dividing by zero, or ..etc

So if an exception occurs or another programmer integrates a premature return statement to fix another bug between the memory allocation and deallocation. In all cases, you will never reach the point where the memory is released. A simple solution to all the above problems is smart pointers.

It is the reason many programmers hate the raw pointers. Many issues are involved with normal pointers like a memory leak, dangling pointer, ..etc.

 

What is a smart pointer?

A smart pointer is an RAII modeled class designed to handle the dynamically allocated memory. Smart pointers ensure that the allocated memory will be released when the smart pointer object goes out of the scope. In this way, the programmer is free from managing dynamically allocated memory manually.

In modern C++ programming (since C++11), the standard library includes smart pointers.  C++11 has three types of smart pointers std::unique_ptrstd::shared_ptr and std::weak_ptr. These smart pointers are defined in the std namespace in the <memory> header file. So you must include <memory> header files before using these smart pointers.

We will see these smart pointers one by one but before using them let’s understand the working of smart pointers and implement our own smart pointers.

 

Smart Pointer Implementation:

Smart pointers are just classes that wrap the raw pointer and overload the -> and * operator. These overloaded operators allow them to offer the same syntax as a raw pointer. It means the objects of the smart pointer class look like normal pointers.

Consider the following simple SmartPointer class. In which we have overloaded the -> and * operators and the class destructor contains the call to delete.

class SmartPointer
{
public:
    // Constructor
    explicit SmartPointer(int* ptr) : m_ptr(ptr) {}

    // Destructor
    ~SmartPointer()
    {
        delete m_ptr;
    }

    // Overloading dereferencing operator
    int& operator* ()
    {
        return *m_ptr;
    }

    // Overloading arrow operator
    int* operator->()
    {
        return m_ptr;
    }

private:
    int* m_ptr;
};

 

You can use SmartPointer class as objects allocated on the stack. Because the smart pointer is declared on the stack is automatically destroyed when they go out of scope. And the compiler takes care of automatically calling the destructor. The smart pointer destructor contains a delete operator which will release the allocated memory.

Consider the following C++ program where I am using the SmartPointer class. You can see the dynamic memory is automatically handled by this class and you don’t need to worry about memory deallocation.

#include <iostream>
using namespace std;

class SmartPointer
{
public:
    // Constructor
    explicit SmartPointer(int* ptr) : m_ptr(ptr) {}

    // Destructor
    ~SmartPointer()
    {
        cout<<"Release the allocated memory\n";
        delete m_ptr;
    }

    // Overloading dereferencing operator
    int& operator* ()
    {
        return *m_ptr;
    }

    // Overloading arrow operator
    int* operator->()
    {
        return m_ptr;
    }

private:
    int* m_ptr;
};


int main()
{
    SmartPointer ptr(new int(27));

    //print the value
    cout<< *ptr <<endl;

    //Assign a value
    *ptr = 10;

    //print the value
    cout<< *ptr <<endl;

    return 0;
}

Output:

Smart pointer in C++

 

The above-mentioned SmartPointer class only works for integers. But you can make it generic using the C++ templates. Consider the below example.

#include <iostream>
using namespace std;

//Generic smart pointer class
template <class T>
class SmartPointer
{
public:
    // Constructor
    explicit SmartPointer(T* ptr) : m_ptr(ptr) {}

    // Destructor
    ~SmartPointer()
    {
        cout<<"Release the allocated memory\n";
        delete m_ptr;
    }

    // Overloading dereferencing operator
    T& operator* ()
    {
        return *m_ptr;
    }

    // Overloading arrow operator
    T* operator->()
    {
        return m_ptr;
    }

private:
    T* m_ptr;
};

class Display
{
public:
    void printMessage()
    {
        cout<<"Smart pointers for smart people\n\n\n";
    }
};


int main()
{
    //With integer
    SmartPointer<int> ptr(new int(27));

    //print the value
    cout<< *ptr <<endl;

    //Assign a value
    *ptr = 10;

    //print the value
    cout<< *ptr <<endl;


    //With custom class
    SmartPointer<Display> ptr1(new Display());
    ptr1->printMessage();

    return 0;
}

Output:

Implement smart pointer in C++

 

Remark: The above smart pointer implementation code is only made to understand the concept of smart pointers. This implementation is not suitable for many practical cases. Also, by no means is it a complete interface of a realistic smart pointer.

 

Types of Smart Pointers:

The following section summarizes the different kinds of smart pointers that are available in C++11 and describes when to use them.

unique_ptr:

It is defined in the <memory> header in the C++ Standard Library. Basically, a unique pointer is an object that owns another object and manages that other object through a pointer. The unique pointer has exclusive ownership of the object it points to.

Let’s understand unique_ptr with an example, suppose U is an object of the unique pointer that stores a pointer to a second object P. The object U will dispose of Pwhen U is itself destroyed. In this context, Uis said to own P.

Also, you must remember that unique_ptr does not share its pointer with any other unique_ptr. This can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it.

unique pointer with move

The following example shows how to create unique_ptr instances and how to move ownership to another unique pointer.

#include <iostream>
#include <memory>
using namespace std;


class Test
{
public:
    void print()
    {
        cout << "Test::print()" << endl;
    }
};

int main()
{
    /*
    Create an unique pointer
    object that store the pointer to
    the Test object
    */
    unique_ptr<Test> ptr1(new Test);

    //Calling print function using the
    //unique pointer
    ptr1->print();

    //returns a pointer to the managed object
    cout << "ptr1.get() = "<< ptr1.get() << endl;

    /*
    transfers ptr1 ownership to ptr2 using the move.
    Now ptr1 don't have any ownership
    and ptr1 is now in a 'empty' state, equal to `nullptr`
    */
    unique_ptr<Test> ptr2 = move(ptr1);
    ptr2->print();

    //Prints return of pointer to the managed object
    cout << "ptr1.get() = "<< ptr1.get() << endl;
    cout << "ptr2.get() = "<< ptr2.get() << endl;

    return 0;
}

Output:

unique pointers C++

 

Remark:Its uses include, exception safety for dynamically allocated memory, passing ownership of dynamically allocated memory to a function, and returning dynamically allocated memory from a function.

 

shared_ptr:

The shared_ptr is a type of smart pointer that is designed for scenarios in which the lifetime of the object in memory is managed by more than one owner. It means the shared_ptr implements semantics of shared ownership.

Like the unique_ptr, shared_ptr is also defined in the <memory> header in the C++ Standard Library. Because it follows the concept of shared ownership, after initializing a shared_ptr you can copy it, assign it or pass it by value in function arguments. All the instances point to the same allocated object.

The shared_ptr is a reference counted pointer. A reference counter is increased whenever a new shared_ptr is added and decreases whenever a shared_ptr goes out of scope or is reset. When the reference count reaches zero, the pointed object is deleted. It means the last remaining owner of the pointer is responsible for destroying the object.

Remark: A shared_ptr is said to be empty if it does not own a pointer.

shared pointer in C++

 

The following example shows how to create shared_ptr instances and how to share the ownership to another shared_ptr pointer.

#include <iostream>
#include <memory>
using namespace std;

class Test
{
public:
    void print()
    {
        cout << "Test::print()" << endl;
    }
};

int main()
{
    /*
    Create an shared ptr
    object that store the pointer to
    the Test object
    */
    shared_ptr<Test> ptr1(new Test);

    //returns a pointer to the managed object
    cout << "ptr1.get() = "<< ptr1.get() << endl;
    //print the reference count
    cout << "ptr1.use_count() = " << ptr1.use_count() << endl;


    cout <<"\nCreate another shared pointer "
         "and Initialize with copy constructor.\n";
    /*
     Second shared_ptr object will also point to same pointer internally
     It will make the reference count to 2.
    */
    shared_ptr<Test> ptr2(ptr1);

    cout << "Prints return of pointer to the managed object\n";
    cout << "ptr1.get() = "<< ptr1.get() << endl;
    cout << "ptr2.get() = "<< ptr2.get() << endl;


    cout <<"\nprint the reference count after creating another shared object\n";
    cout << "ptr1.use_count() = " << ptr1.use_count() << endl;
    cout << "ptr2.use_count() = " << ptr2.use_count() << endl;

    // Relinquishes ownership of ptr1 on the object
    // and pointer becomes NULL
    cout <<"\nprint the reference count after reseting the first object\n";
    ptr1.reset();
    cout << "ptr1.get() = "<< ptr1.get() << endl;
    cout << "ptr2.use_count() = " << ptr2.use_count() << endl;
    cout << "ptr2.get() = "<< ptr2.get() << endl;

    return 0;
}

Output:

ptr1.get() = 0xf81700
ptr1.use_count() = 1

Create another shared pointer and Initialize with copy constructor.
Prints return of pointer to the managed object
ptr1.get() = 0xf81700
ptr2.get() = 0xf81700

print the reference count after creating another shared object
ptr1.use_count() = 2
ptr2.use_count() = 2

print the reference count after reseting the first object
ptr1.get() = 0
ptr2.use_count() = 1
ptr2.get() = 0xf81700

 

weak_ptr

A weak_ptr is a smart pointer that stores a weak reference to an object that is already managed by a shared_ptr. The weak_ptr does not take ownership of an object but it acts as an observer (weak_ptrs are for shared observation). This means itself it does not participate in the reference counting to delete an object or extend its lifetime. We mainly use the weak_ptr to break the reference cycles formed by objects managed by std::shared_ptr.

A weak_ptr can be converted to a shared_ptr using the member function lock to access the object. It means you can use a weak_ptr to try to obtain a new copy of the shared_ptr with which it was initialized. If the memory has already been deleted, the weak_ptr’s bool operator returns false.

 

Recommended Articles for you:

 

References:
Dynamic memory management.