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
Using C++ Without the Heap
Want to use C++, but worried about how much it relies on dynamic memory allocations? Our course provides a hands-on approach for learning a diverse set of patterns, tools, and techniques for writing C++ code that never uses the heap.
Learn More on the Course Page