C++ Smart Pointers

If you work with pointers in C++ before C++11, then you have to create and destroy it manually. This manual management of pointers has certain problems that can quickly escalate to some catastrophic memory bugs which are hard to debug. The common issues related to the manual management of pointers are:

  • Memory Leaks
  • Dangling Pointers
  • Unclear Ownership

Hence we have to take utmost care when dealing with manual pointer management.

Traditional Pointers

We use the new& delete operator for dynamic allocation and deallocation of object pointers on heap. Let’s explore an example:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

class Car {
public:
Car() { std::cout << "Car created" << std::endl; }
~Car() { std::cout << "Car destroyed" << std::endl; }
void hello() { std::cout << "Hello from car" << std::endl; }
};

int main() {
return 0;
}

We created a Car class with constructor and destructor because this will help us to see the messages on creation and destruction of a car object. Now if we are not using C++11 then we have to:

  • Create object using new
  • Delete the object using delete
1
2
3
4
5
int main() {
Car *c1 = new Car();
c1->hello();
delete c1;
}

If we execute the code above we can see the logs are hinting towards, creation and destruction of the object.

1
2
3
Car created
Hello from car
Car destroyed

Issues with Manual Management

We already saw the prominent problems with manual management, let’s have a look at the issues we mentioned above.

Memory Leaks

This is the scenario when the program is borrowing memory from the OS but is not returning that. The programs are allocated a fixed amount of memory and if we keep borrowing and do not return them at some time in the future our program will crash due because the OS will through error of process out of memory.

During manual management it is common to forget the call to delete which leads to memory leaks.

Dangling Pointer

This is the case where the pointer variable we created cannot be traced. For example we are calling the hello method and if it throws and exception we cannot reach back to the pointer variable c1.

Here’s an example:

1
2
3
4
5
6
7
8
9
// create a pointer 
int* p = new int(10);

// deleting the allocated memory
delete p;

// this operation has undefined behavior
// ptr is now dangling
*ptr = 20;

We can solve such issues by explicitly assigning NULL to the variable p.

1
2
3
4
5
6
// create a pointer 
int* p = new int(10);

// deleting the allocated memory
delete p;
p = NULL;

Unclear Ownership

Suppose we have a function that process the car for maybe painting or delivery and accidentally someone calls delete on that. There’s no restriction to this and this can be easily done which created undefined behavior and logical issues at runtime.

1
2
3
4
5
6
7
8
9
void process_car(Car *c) {
// some logic

// `process_car` is not the owner but can easily delete it
delete c;

// some logic
return c;
}

C++11 at Rescue

The C++ committee has been listening to a lot of problems and C++11 is the modern and major revision to the C++ programming language that has redefined how we think and code in C++. The v11 has major and significant changes that has profound impact. It has tried it’s best to make the language safer and less verbose.

One of the safety addition to the C++ 11 is the concept of Smart Pointers.

Smart Pointers automates the allocation and deallocation of memory so that the programmer is not burdened with the manual management which is prone to fatal errors as we saw above.

Smart Pointers

Smart pointers in C++ are class templates that automatically manage dynamic memory. They are like the regular pointers but automatically delete the pointer when the variable goes out of scope. We can use the <memory> header file to use them. There are three types of smart pointers introduced:

  • Unique Pointer std::unique_ptr
  • Shared Pointer std::shared_ptr
  • Weak Pointer std::weak_ptr

Unique Pointer

Let’s try and code the same program above using unique pointer instead of regular pointer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <memory>

class Car {
public:
Car() { std::cout << "Car created" << std::endl; }
~Car() { std::cout << "Car destroyed" << std::endl; }
void hello() { std::cout << "Hello from car" << std::endl; }
};

int main() {
// `new Car()` is being used as a function argument to the constructor of `unique_ptr`
std::unique_ptr<Car> c1(new Car());
c1->hello();
}

A unique_ptr owns a piece of memory exclusively. This is like a single room key that can be owned by a single person at any given tie. This pointer automatically cleans up the memory when it goes out of scope which helps prevent memory leaks. Unique pointers cannot be copied but we can still transfer ownership using std::move when required.

Shared Pointer

A shared_ptr allows multiple parts of a program to share the same piece of memory. The smart pointer class internally keeps a reference count, and once the last shared_ptr is destroyed, the memory is automatically released.

1
2
3
4
5
6
7
8
9
10
int main() {
std::shared_ptr<Car> c1 = std::make_shared<Car>();
{
std::shared_ptr<Car> c2 = c1; // another owner
c2->hello();
std::cout << "Use count: " << c1.use_count() << std::endl;
} // c2 destroyed, but car still alive

std::cout << "Use count after inner scope: " << c1.use_count() << "\n";
}

This makes it useful when several objects or functions need to work with the same resource.

Weak Pointer

weak_ptr observes a shared_ptr without actually owning the memory. Since it doesn’t increase the reference count, it helps avoid circular ownership problems. To safely use the object, we first turn the weak_ptr into a shared_ptr by calling lock().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
std::shared_ptr<Car> owner = std::make_shared<Car>();
std::weak_ptr<Car> viewer = owner;

// converting to shared pointer
if (auto temp = viewer.lock()) {
temp->hello();
}

owner.reset(); // Car destroyed here

if (viewer.lock() == nullptr) {
std::cout << "Car no longer exists" << std::endl;
}
}

Closing Notes

If you are learning C++ or stating a new project in C++ I would highly encourage you to go through the C++ 11 specifications so that you can develop modern and safe applications.

Happy Coding. Bye!