Ditch Your C-style Pointers for Smart Pointers

Similar to my recommendation to ditch your built-in arrays, it's time to ditch your basic raw pointers for C++ smart pointers. Using raw pointers leave you susceptible to a variety of bugs:

  • Memory leaks
  • Freeing memory that shouldn't be freed (e.g. pointer to statically allocated variable)
  • Freeing memory incorrectly - free() vs aligned_free(), delete vs delete[]
  • Using memory that has not yet been allocated
  • Thinking memory is still allocated after being freed because the pointer itself was not updated to NULL

Most of these problems arise from the number of details we have to keep in mind in order to write bug-free code. Luckily, C++ provides three smart pointer types that eliminate many of the raw headaches:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

If you're unfamiliar with these interfaces, please review my introduction to C++ smart pointers before continuing. Since I've already covered background information on using these pointers, I'm going to focus on appropriate use cases for each pointer.

What Pointer Should I Use, and When?

As a general rule of thumb, I recommend using std::unique_ptr as your default smart pointer. If you find that you need to share data or utilize reference counting, you should use a std::shared_ptr. If you need to reference shared data but don't want to contribute to the reference count, use a std::weak_ptr.

std::unique_ptr

std::unique_ptr represents data which has only one owner at any given time. It should be your default choice when you need a smart pointer. You can move a std::unique_ptr around to keep it alive, but there can only be one owner of the data. After moving the pointer, the previous pointer object is invalidated.

There's also a std::unique_ptr<T[]> type which represents arrays. I tend to stick to the normal std::unique_ptr type and utilize a std::vector or std::array in instances where I want to deal with arrays.

You will want to utilize std::unique_ptr in cases such as:

  • Dynamic memory allocations that are only accessed in one scope at a time (can be moved around)
    • Buffers for USB transfers
    • Local object allocations (for large objects that you don't want on the stack)
    • Thread-specific allocations
  • Factory function output (i.e. unique instances)
  • Class members that are pointers

std::shared_ptr

You should use std::shared_ptr when there is shared ownership of an object. std::shared_ptr uses reference counting and destroys the object when there are no instances in use. I primarily use std::shared_ptr for sharing objects across threads. However, make sure that each thread has its own std::shared_ptr, rather than sharing the same one!

Note that using std::shared_ptr can make your code harder to follow, as the lifetime of the object may not be immediately obvious to other developers reviewing your code. Don't use a std::shared_ptr if a std::unique_ptr will suffice.

std::weak_ptr

You should use a std::weak_ptr when you want to check or use a shared memory resource, but don't want to be responsible for keeping the data alive by contributing to the reference count. A std::weak_ptr is also useful in situations where your program will react differently depending on whether the resource is still alive.

I've seen many folks discuss using a std::weak_ptr to break a cycle that would keep your objects alive (such as in a linked list), but I have not run into such problems in my own code.

When should I not use smart pointers?

The primary case where I avoid smart pointers is in API design. Unless the underlying function is going to take ownership of the smart pointer data, my APIs primarily utilize raw pointers or references (since they can't be NULL). However, you should be sure that the raw pointer you pass will not outlive the smart pointer container.

Since smart pointers are indicators about data ownership, I tend to avoid them when I don't own the data. Such a case occurs when referencing objects with ownership over the current scope (e.g. "parent" pointers). If a parent object owns the current scope and has a guaranteed existence, use a reference. If the parent object owns you but there is the possibility of having no owner, use a pointer.

While I don't think the memory or operational costs of smart pointers makes a difference, if your system is sufficiently constrained you will need to consider std::shared_ptr object overhead (control block memory requirements and reference counting operations).

Further Reading