Pointers are a great asset, and can be used to solve a variety of different problems. However, Pointers are not very easy to use, and when used for complex problems, even a slight mistake can ruin everything. Tracing the memory and addresses, ensuring that there are no dangling pointers etc, can be a rather daunting tasks. Luckily, C++ has introduced the concept of Smart Pointers to help us.

Like Vectors, Smart Pointers are templates, which means when we create a Smart pointer, we need to specify the type to which the pointer will point.


Why do we need Smart Pointers in C++?

Unlike stack memory, dynamically allocated memory must be manually deleted. Whenever we allocate memory on the heap using the new keyword, we have to manually free it later using the delete keyword. This can be quite the hassle in larger applications, and often cause problems that are hard to track down.

In general, Dynamically allocated memory is tricky to handle. Luckily C++ gives us a better way of managing this with “Smart Pointers”. These pointers, as the name implies are able to automatically delete themselves when they go out of scope, removing the need for you to do it yourself. This saves us from many issues that using the new and delete keywords can bring.

There are three types of smart pointers in C++, shared pointers, unique pointers and weak pointers. We’ll discuss each of them individually in the sections below.


Shared Pointers

The first main type of Smart Pointer, is the Shared Pointer. It’s a type of pointer that allows multiple pointers to refer to the same object. Shared Pointers use what we call reference counts to keep track of the number of pointers to an object. Everytime a new pointer points to the object, the reference count is incremented.

The Object is deleted when this reference count drops to 0, and the memory is then freed. The reference count drops when shared pointers are either destroyed, or they go out of scope. When the last shared pointer to an object is destroyed, the shared pointer class deletes the object.

Creating a Shared Pointer

Let’s create some shared pointers. The below examples should help you understand the syntax.

// Creates a shared pointer that can point to int
shared_ptr<int> ptr;   

// Creates a shared pointer that can point to a list of ints      
shared_ptr<list<int>> ptr2;   

We can now begin giving this pointer something to point to. While we can use the regular methods as well as the new keyword, the safest way (and most recommended) way is to use the make_shared function. This function allocates and initializes an object in dynamic memory and returns a shared pointer that points to that memory. Pretty similar to the new function.

// Now points to an address which stores int value 19
shared_ptr<int> ptr1 = make_shared<int>(19);  

Everything else about the shared pointer is just like that of a regular pointer. You can use the * operator on them, you can assign an address to them with &, etc.

Shared Pointer and it’s Scope

A small example we have made to demonstrate how the shared pointer, and reference counts work.

int main() {
    shared_ptr<int> p1 = make_shared<int>(2);
    cout << p1.use_count() << endl;

    if (1) {
        shared_ptr<int> p2 = p1;
        cout << p1.use_count() << endl;
    }

    cout << p1.use_count() << endl;
}

When we created the first pointer, p1, reference count was set to 1. After creating a new pointer, p2, in the if statement block, the reference count was incremented to 2. Once we exited that scope of the if statement, the pointer p2 was destroyed and the reference count dropped to 1 again.

1
2
1


Unique Pointer

The second type of Smart Pointer available in C++.

The difference between the Unique Pointer and the shared Pointer is that unlike shared, where an object can have multiple shared pointers, only one unique pointer can be pointing to an object at any given time. Hence the name “Unique”.

When the Unique pointer, pointing to an object is destroyed, the object is also destroyed, freeing up memory. There is no “make_shared” equivalent for unique_ptr, so we will have to make do with new.

Creating Unique Pointers

There isn’t any special function for creating unique pointers, leaving us with the new keyword.

int main() {
    unique_ptr<int> p1(new int(2));

    cout << *p1 << endl;

    unique_ptr<int> p2 = move(p1);

    cout << *p2 << endl;
    // cout << *p1 << endl; // Throws an error
}

If we uncomment the line commented line in the above code, there will be an error. Why? Because there cannot be more than one pointer pointing to the same object.

2
2
// Third output is Error

Note: There is one exception to the “only one pointer at a time”. We can copy or assign a unique_ptr that is about to be destroyed. For example, when we return a unique pointer from a function. The compiler will know that the object is about to be destroyed, and performs a special copy.


Weak Pointer

Weak Pointers are the third type of Smart Pointers in C++.

The Weak Pointer class is a sort of extension, or companion of the Shared Pointer. It does not manage the life time of the object that it points to, unlike the other two smart pointers. It instead, points to an object that is managed by a shared pointer, but unlike a regular shared pointer, it does not increase the reference count. This is why it’s called a “weak” pointer.

You can’t directly use a Weak Pointer, and need to use the lock() function. This returns a shared pointer to the object it was pointing to, if the object still exists. If the reference count has dropped to zero however, and the object is deleted, it will return NULL.

Using Weak Pointers

Here is the first example we have on Weak Pointers, which is meant to emphasize that Weak Pointers have no effect on the Reference Count. Even after initializing the Weak Pointer with the address held by the shared pointer, the reference count remained one.

int main() {
     shared_ptr<int> sp = make_shared<int>(24);

     cout << sp.use_count() << endl;

     weak_ptr<int> wp(sp);

     cout << sp.use_count() << endl;
}

Output:

1
1

Here we have another example where we demonstrate the Connection of the Weak Pointer with the object it’s pointing to.

The int object 53 has been created in the if statement below. Once the if statement’s code block ends, this object is destroyed as well due to going out of scope. After it has been destroyed, we can no longer access it. We have attempted to access it twice, once while we it’s still alive, and once after it’s been destroyed.

int main() {
    weak_ptr<int> wp;

    if(1) {
        shared_ptr<int> sp = make_shared<int>(53);
        wp = sp;

        auto p = wp.lock();
        if (p) {
            cout << "Connection Intact" << endl;
        }
        else {
            cout << "Pointing to Null" << endl;
        }
    }

    auto p = wp.lock();
    if (p) {
        cout << "Connection Intact" << endl;
    }
    else {
        cout << "Pointing to Null" << endl;
    }

}

Output:

Connection Intact
Pointing to Null

These are useful for scenarios where you don’t want to increment the reference count, yet hold a connection to the object.


This marks the end of the C++ Smart Pointers tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments