18: Advanced

UC Irvine - Fall ‘22 - ICS 45C

Quick list of things I want to talk about:

  • RAII
  • Smart pointers

Expanded notes:

This set of notes gives you a little insight into other things you might want to read/use in C++. None of this will be covered in quizzes or projects in this course, although they can be very helpful! We’ll focus mainly on a single topic: smart pointers. But before that, let’s discuss a code design called Resource Acquisition is Initialisation (RAII).

RAII states that we should acquire resources we plan to use when we initialize them (e.g., request new memory in pointer initialization) but also that any resources should be freed when our object dies. That means that whenever our code finishes the current scope (function, loop, etc.), any variables inside should free any resources that they had requested.

In our projects, we tried doing that with destructors. Whenever our objects used dynamic memory, we used a destructor to deallocate all requested memory. In this note, we’ll discuss another way to achieve leak-proof code.

Smart pointers

So far in this course, we’ve used regular pointers – sometimes called raw pointers. When using raw pointers, you need to make sure you keep track of references, and whenever you’re done using that memory, you need to manually clean it up with a delete command.

A smart pointer takes care of that for you, as it knows that it needs to delete the memory whenever its scope ends, so it prevents memory leaks automatically for you (assuming it points to a base type or a type with a destructor). We’ll talk about two types of smart pointers below.

Unique pointers

The first type of smart pointer we’ll talk about is the unique pointer. A unique pointer is a pointer that knows that it is the only one pointing to a specific object. It prohibits other pointers from copying its reference, and whenever it gets deleted, it ensures that the memory it was pointing to is also deleted.

Let’s take a look at an example that has memory leaks using regular pointers:

#include <iostream>

using std::cout;

int main() {
  int y = 10;
  while (true) {
    int *x = new int(--y);
    cout << *x << '\n';
    if (*x == 0) {
      break;
    }
  }
  return 0;
}

In this code, we’re initializing x with an int pointer and decreasing y; whenever x (and y) are 0, we exit the loop. Is there anything wrong here? We forgot to add delete! So this code is leaking all ints we create.

So a better solution is to use a smart pointer. Let’s try using a unique pointer:

#include <iostream>
#include <memory>

using std::cout;
using std::unique_ptr;

int main() {
  int y = 10;
  while (true) {
    unique_ptr<int> x(new int(--y));
    cout << *x << '\n';
    if (*x == 0) {
      break;
    }
  }
  return 0;
}

Whenever the unique_ptr x goes out of scope (after each loop iteration), the pointer knows to free the memory it had requested for the int. So it prevents any leaks and saves us from having to remember to delete things every time.

However, since this is a unique pointer, you can’t have two unique pointers that point to the same object:

#include <iostream>
#include <memory>

using std::cout;
using std::unique_ptr;

int main() {
  unique_ptr<int> x(new int(10));
  unique_ptr<int> y = x;  // error here!
  
  cout << *x << '\n';
  cout << *y << '\n';

  return 0;
}

Shared pointers

If you want multiple pointers to the same object, you can use a shared_ptr. A shared_ptr is similar to a unique_ptr, the difference is that you can have multiple references to the same object, and whenever the last reference goes out of scope, only then is the object deleted.

For example:

#include <iostream>
#include <memory>

using std::cout;
using std::shared_ptr;

void fun(shared_ptr<int>& original_x) {
  shared_ptr<int> new_x(original_x);
  cout << *new_x << '\n';
}

int main() {
  shared_ptr<int> x(new int(10));
  fun(x);

  return 0;
}

Whenever our function fun finished executing, the new_x shared_ptr knows that it’s not the last reference to our int object. So it does not delete the object. Then, whenever the main function finishes, the x shared_ptr knows that it was the last reference to our int object, so it deletes the object and prevents any leaks. As this example shows, you can have multiple pointers that share the same object when using shared pointers, which is impossible with unique pointers.

Further reading

References