Codementor Events

Smart Pointers in C++ - Part 1

Published Jun 24, 2021Last updated Jul 23, 2021
Smart Pointers in C++ - Part 1

Introduction

C++ defines smart pointers which can be used to replace raw pointers. Smart pointers can be used to ensure that the code is free of memory leaks. Smart pointers are defined in the <memory> header under the std namespace. Smart pointers are just classes that wrap raw pointers and provide functionality to make the process of resource acquisition and release free of errors.

Let's have a look at a problem that usually occurs with raw pointers.

Example 1

int main() {
  // dynamically allocate heap memory to Person object
  // name - "John Doe", age - 45 
  Person *p = new Person("John Doe", 45);

  // do something with the Person object

  return 0;
}

What's wrong with the above code? The fact that the memory allocated for p was not freed before main returned. So we need to add delete p; statement before return 0;. But the programmer sometimes forgets to do this, and we get a memory leak.

Another problem could be double free error. This error occurs when we try and free a specific dynamically allocated memory more than once.

Example 2

int main() {
  // dynamically create an int
  int *p1 = new int(10);

  // some code

  // p2 points to the same int where p1 points
  int *p2 = p1;

  // some more code

  delete p2;

  // trying to access value at p1
  cout << *p1; // error

  delete p1; // error
  return 0;
}

The above code has two problems. First, we are trying to access the value pointed to by p1, but p1 is a dangling pointer. Why? Because we just cleared the memory allocated for the int using delete p2! So we cannot access the value pointed to by p1 anymore. Second, when we try and delete the memory allocated to p1. p1 is not pointing to a valid int. So this gives us a double free error.

These problems can be easily avoided by using smart pointers.

Smart pointers to the rescue

Let's try and use a smart pointer for Example 1.

Example 1 (revised)

#include <memory>
int main() {
  // dynamically allocate heap memory to Person object
  // name - "John Doe", age - 45 
  std::unique_ptr<Person> p1 (new Person("John Doe", 45));

  // do something with the Person object

  return 0;
}
// p1 is deleted automatically

We don't use delete p1 to free the memory allocated for the Person object. When p1 goes out of scope, the unique_ptr desctructor runs and frees up the memory. So, when using smart pointers, the programmer can forget about memory management and focus on the other important aspects of the application. Memory management will be automatically handled by smart pointers.

How do smart pointers avoid the double free error from Example 2? We will be replacing raw pointer with unique_ptr. unique_ptr objects (yes, they are technically objects) are special smart pointers with a constraint that no two unique_ptr objects can point to the same object (more about this later).

Example 2 (revised)

#include <memory>
int main() {
  // dynamically create an int
  std::unique_ptr<int> p1 (new int(10));

  // some code

  std::unique_ptr<int> p2;

  p2 = p1; // compiler error - copy assignment not allowed

  // p2 points to the same int where p1 points
  p2 = std::move(p1); // OK - move assignment must be used.

  // p1 is invalidated. p2 takes ownership of the int.

  // some more code

  // delete p2; // we don't do this as p2 is not a raw pointer.

  // trying to access value at p1
  cout << *p1; // runtime error as p1 is invalidated

  // But we can first check whether a smart pointer is valid or not
  if (static_cast<bool>(p1)) {
    // valid
  } else {
    // invalid
  }

  cout << *p2; // 10
  return 0;

So we first created a new int, owned by p1, using a constructor. Then, we just declared p2. This creates a default unique_ptr object p2 that owns nothing. Then we move assigned p2 by giving it the ownership of the int previously owned by p1. This invalidates p1.

Now, we have introduced some new concepts in this example.

  • First, we say that a smart pointers takes ownership of an object (or a primitive data type; the memory allocated to it, in general), as opposed to saying points to.
  • Second, we can dereference smart pointers just like raw pointers (that's because the operator * is overloaded for unique_ptr and other types of smart pointers too. We are yet to discuss them.).
  • Third, move semantics. We have to use move semantics (meaning move constructor and move assignment) when dealing with unique_ptr smart pointers. Copying is not allowed as that would defeat the purpose of a unique_ptr. As the name suggets, the pointer has to be unique, meaning it can be the only one owning a specific object.

Stay tuned for knowing more unique_ptr and other types of smart pointers.
Can't wait to find out more? Click for Part 2.

Discover and read more posts from Sandesh Patil
get started