Smart Pointers in C++

Filed Under: C++
Smart Pointers Cpp

In this article, we’ll take a look at how we can use Smart Pointers in C++.

Smart Pointers are an abstract interface to actual pointers (also called raw pointers), but with the additional benefit of automatically managing resources and freeing memory.

Smart Ptr Example
Smart Ptr Example

The design logic for a smart pointer is to implement it as a class.

This is because a class has a destructor, which will trigger automatically when an object is at the end of its scope. Smart Pointers use the destructors to free up resources automatically.

You don’t need to free smart pointers explicitly, and hence they are “smart” pointers since they know when they need to free resources.

Let’s understand more about these types of smart pointers in C++, using examples.


Using Smart Pointers in C++

These pointers are defined in the standard C++ library, in the std namespace. To include some of these pointers, we also need the <memory> header file.

There are three commonly used smart pointers, called:

  • unique_ptr
  • shared_ptr
  • weak_ptr

Let’s go through them one by one.


unique_ptr – Smart pointer in C++

This is a smart pointer that will only permit one owner of the pointer. That is, unique_ptr can contain, at maximum, only a single raw pointer (“unique” pointer) that points to a single memory location.

Keep this as your default choice, unless you’re working with multiple threads, or whenever you need multiple owners to the same raw pointer.

To create a smart pointer using unique_ptr, there are two ways of achieving this:

Case 1: Creating a Unique Smart Pointer from an object

If you have an object and want to create a smart pointer in C++ which points to this object, use the below syntax:

std::unique_ptr<myType> my_ptr(new MyType());

The above line creates a new raw pointer of type MyTyp. We pass this to create our smart pointer using std::unique_ptr<myType>.

You can now call methods on the object using the smart pointer handle, using the -> operator.

// Call a method on the object
my_ptr->ObjectMethod();

You can also free the memory that my_ptr owns, using:

my_ptr.reset();

We use the . operator, since we apply this to the smart pointer directly.

Case 2: Construct a new unique pointer to a memory location

If you want to construct a new unique pointer, simply use the make_unique() function.

std::unique_ptr<std::string> = std::make_unique<std::string>("Hello from JournalDdev");

This creates a unique pointer to the string “Hello from JournalDev”.

Other operations remain the same.

Move a unique pointer

Since a unique_ptr is unique for any memory location, we cannot have a unique pointer to multiple objects. So we can only move a unique_ptr.

std::unique_ptr<int> unique_ptr_1(new int(1000));
// Move the raw pointer from unique_ptr_1 TO unique_ptr_2
std::unique_ptr<int> unique_ptr_2 = std::move(unique_ptr_1);

// Now, the data is pointed to by unique_ptr_2
unique_ptr_2->doSomething();

Let’s now look at an example to see how the whole thing works:

#include <iostream>
#include <memory>

class MyClass{
    public:
        int a;
        MyClass() { std::cout << "Default Constructor\n"; a = 10; }
        MyClass(int value = 100) { std::cout << "Parametrized Constructor\n"; a = value; }
};

int main() {
    // Object of MyClass
    MyClass my_obj(250);

    // Construct the pointer using make_unique()
    std::unique_ptr<MyClass> my_ptr_1 = std::make_unique<MyClass>(my_obj);
    // Create a new object and get the unique_ptr to it
    std::unique_ptr<MyClass> my_ptr_2(new MyClass(500));
    
    std::cout << "Value of a (my_ptr_1): " << my_ptr_1->a << std::endl;
    std::cout << "Value of a (my_ptr_2): " << my_ptr_2->a << std::endl;

    // Both the pointers will automatically get destroyed, since the objects are at the end of their scopes
    // But if we want to explicitly free the raw pointer, we can use unique_ptr.reset()
    std::cout << "Freeing the pointers...\n";
    my_ptr_1.reset();
    my_ptr_2.reset();
    return 0;
}

Output

Parametrized Constructor
Parametrized Constructor
Value of a (my_ptr_1): 250
Value of a (my_ptr_2): 500
Freeing the pointers...

shared_ptr

The shared_ptr is another choice for a smart pointer in C++, if you want to deal with multiple owners. This also maintains a reference count of all the pointers which point to the object.

Similar to unique_ptr, the syntax is almost the same, except that you now return a shared pointer instead. You are also now allowed to pass multiple objects to the invocation, so that the shared_ptr points to all of them.

To construct a new shared pointer, use the make_shared() function.

// my_ptr points to two objects
std::unique_ptr<myType> my_ptr(new MyType(1), new MyType(2));

// A shared pointer to 2 strings
std::unique_ptr<string> my_str_ptr = std::make_shared<string>("Hello", "How are you?");

There is no requirement of using std::move, since a shared pointer can point to multiple locations.

To get the current reference count for any/all of the shared pointers, use my_shared_ptr.use_count(). This will automatically update when we create or free a shared pointer to the common object.

We’ll look at an example regarding shared_ptr.

#include <iostream>
#include <memory>

class MyClass{
    public:
        int a;
        MyClass() { std::cout << "Default Constructor\n"; a = 10; }
        MyClass(int value = 100) { std::cout << "Parametrized Constructor\n"; a = value; }
};

int main() {
    // Objects of MyClass
    MyClass my_obj(250);

    // Construct a pointer using make_shared
    std::shared_ptr<MyClass> my_ptr_1 = std::make_shared<MyClass>(my_obj);
    std::cout << "Current Reference Count of my_ptr_1 = " << my_ptr_1.use_count() << std::endl;
    // And another shared pointer to the same location!
    std::shared_ptr<MyClass> my_ptr_2 = my_ptr_1;

    std::cout << "Current Reference Count of my_ptr_1 = " << my_ptr_1.use_count() << std::endl;
    std::cout << "Current Reference Count of my_ptr_2 = " << my_ptr_2.use_count() << std::endl;

    std::cout << "Value of a (from dereferencing my_ptr_1): " << my_ptr_1->a << std::endl;
    std::cout << "Value of a (from dereferencing my_ptr_2): " << my_ptr_2->a << std::endl;

    // Both the pointers will automatically get destroyed, since the objects are at the end of their scopes
    // But if we want to explicitly free the raw pointer, we can use unique_ptr.reset()
    std::cout << "Freeing the pointers...\n";
    my_ptr_1.reset();
    std::cout << "Freed my_ptr_1\n";
    std::cout << "Current Reference Count of my_ptr_1 = " << my_ptr_1.use_count() << std::endl;
    std::cout << "Current Reference Count of my_ptr_2 = " << my_ptr_2.use_count() << std::endl;
    my_ptr_2.reset();
    std::cout << "Freed my_ptr_2\n";
    std::cout << "Current Reference Count of my_ptr_1 = " << my_ptr_1.use_count() << std::endl;
    std::cout << "Current Reference Count of my_ptr_2 = " << my_ptr_2.use_count() << std::endl;
    return 0;
}

Output

Parametrized Constructor
Current Reference Count of my_ptr_1 = 1
Current Reference Count of my_ptr_1 = 2
Current Reference Count of my_ptr_2 = 2
Value of a (from dereferencing my_ptr_1): 250
Value of a (from dereferencing my_ptr_2): 250
Freeing the pointers...
Freed my_ptr_1
Current Reference Count of my_ptr_1 = 0
Current Reference Count of my_ptr_2 = 1
Freed my_ptr_2
Current Reference Count of my_ptr_1 = 0
Current Reference Count of my_ptr_2 = 0

As you can observe, both pointers point to the same object. After any one of them is freed, again, the reference counts are updated to reflect the changes.


weak_ptr

The weak_ptr is a smart pointer in C++ that is similar to shared_ptr, but it does not maintain a reference count. This is useful in cases where objects of one class can point to another, and vice-versa.

Consider the following scenario, where you have two classes A and B, having a shared_ptr member to the other class (i.e an object of A can point to an object of B, and vice-versa). Now, when you create two objects of A and B, make them point to each other, using the shared_ptr member, what happens now?

#include <iostream>
#include <memory>

// Forward declare class B
class B;

class A{
    public:
        int a;
        std::shared_ptr<B> ptr;
        A(int value = 200) { a = value; }
        ~A() {std::cout << "Destructor for A\n"; }
};

class B{
    public:
        int a;
        std::shared_ptr<A> ptr;
        B(int value = 200) { a = value; }
        ~B() {std::cout << "Destructor for B\n"; }
};

int main() {
    std::shared_ptr<A> ptr_a = std::make_shared<A>(750);
    std::shared_ptr<B> ptr_b = std::make_shared<B>(750);
    
    // Make ptr_a point to ptr_b
    ptr_a->ptr = ptr_b;
    // And make ptr_b point to ptr_a
    ptr_b->ptr = ptr_a;

    return 0;
}

Output


Yes, we don’t get any output, even though we expect our destructor to clean-up for us! What is happening?!!

This is an example of a Circular Reference, where pointers point to each other. Since these pointers are shared_ptrs, they also have their reference counts as 2.

Circular Reference Shared Ptrs
Circular Reference Shared Ptrs

When the destructor of the ptr_a‘s object tries to clean-up, it finds out that ptr_b is pointing to ptr_a, so it cannot simply remove it. Similarly, the destructor for ptr_bs object also cannot clean-up, resulting in no output!

To eliminate this problem, we can use the weak_ptr smart pointer inside our class. Since the weak pointer does not contribute to the reference count, we can make one of the two pointers as a weak pointer, for example, ptr_b to ptr_a.

Since a weak pointer does not own the data, we can now clean up the shared pointer a, after which we can clear b also!

So, we can make the ptr member of class B as a weak_ptr. (You could also do it for A, but we’ll do it for B)

Resolve Dependency
Resolve Dependency

Let’s now make the changes to our code snippet to make this work.

#include <iostream>
#include <memory>

// Forward declare class B
class B;

class A{
    public:
        int a;
        std::shared_ptr<B> ptr;
        A(int value = 200) { a = value; }
        ~A() {std::cout << "Destructor for A\n"; }
};

class B{
    public:
        int a;
        std::weak_ptr<A> ptr;
        B(int value = 200) { a = value; }
        ~B() {std::cout << "Destructor for B\n"; }
};

int main() {
    std::shared_ptr<A> ptr_a = std::make_shared<A>(750);
    std::shared_ptr<B> ptr_b = std::make_shared<B>(750);
    
    // Make ptr_a point to ptr_b
    ptr_a->ptr = ptr_b;
    // And make ptr_b point to ptr_a
    // Since ptr_b->ptr is a weak pointer, we don't have the problem now!
    ptr_b->ptr = ptr_a;

    return 0;
}

Output

Destructor for A
Destructor for B

Now, we’ve changed the type of the smart pointer of class B to weak_ptr. So now, there is no more circular dependency, and we’ve finally solved this problem!


General Programming Guidelines

To summarize this article, I’ll provide you with a small list of guidelines to follow when working with SMART pointers in C++.

  • Always try to use smart pointers whenever you can. In most cases, use unique_ptr, if you’re not dealing with sharing a memory location with multiple pointers/threads.
  • Otherwise, use the reference-counted shared_ptr, when dealing with multiple owners.
  • If you want to examine an object, but not require that the object itself exists, use a weak_ptr. This pointer does not contribute to the reference count and is suitable for such tasks.
  • Whenever you directly use raw pointers, try to make sure it is contained only in a very small amount of code, or where you must absolutely use it, to reduce the overhead of using smart pointers.

Conclusion

In this article, we learned how we could use smart pointers in C++, along with some examples for each type.

For more articles, go through our C++ tutorials section! Do leave any feedback in the comment section below!

References

  • Microsoft Documentation on Smart Pointers (Is a very good resource, even if you’re working on Linux. Highly recommended read)

Leave a Reply

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

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages