Search

10.4 — Introduction to ownership and smart pointers

Pointers and dynamic memory allocation are like wild beasts -- powerful, but also dangerous. Misuse of pointers and dynamic memory allocation can result in memory leaks, memory corruption, undefined behavior, and application crashes. And this is compounded by the fact that memory management in C++ is one the hardest thing to get right.

While rigorous testing can identify various pointer and dynamic memory allocation problems, good programming practices and effective use of the standard library can help prevent such errors from occurring in the first place.

Before we dig into some of the functionality that the standard library has available to help, let’s set the stage with some additional context.

What is ownership?

The owner of a resource is the one who is responsible for cleanup of that resource. In the context of dynamic allocation, the owner of the memory is responsible for deallocating the memory when it is no longer needed.

Every dynamically allocated resource must have one (and only one) owner. If there are no owners, the resource will not be deallocated properly, and memory will be leaked. If there is more than one owner, when the second owner goes to deallocate the object, it will try to deallocate an object that has already been deallocated, causing undefined behavior.

Raw pointers are poor owners

Non-member pointers tend to make poor owners. Consider the following example:

If the code takes the early exit, ptr never gets deallocated.

This can also happen in other ways: an exception can cause a function to exit early, goto or continue or a logic error can jump over the line of code that performs deallocation, etc… While it is easy to see this happening in the above example, in a non-trivial piece of software, these can be hard to find.

The core of the problem here is that ptr doesn’t have any automatic cleanup, so we have to ensure the line of code that deallocates ptr gets executed regardless of the execution path through the function. Testing all possible paths can be challenging or time-consuming.

Transferring ownership also causes its share of problems. Consider a function like the following:

This function returns a pointer to the newly created Ball -- and expects the caller to take ownership of said Ball (and deallocate it when done). But this transfer of ownership is never explicitly documented, so if the owner doesn’t actually deallocate the Ball, then we have a memory leak.

Now consider this very similar function:

Note that the function signature is the same, but since this Ball is not dynamically allocated, the caller is not expected to be an owner. If the caller tries to delete this Ball, undefined behavior will result.

This same challenge occurs (in the opposite direction) with function parameters:

There is no way to tell via the function signature whether SomeFunction merely uses ball, or whether the function assumes ownership of ball (and possibly deallocating it).

One of the best things about classes is that they contain destructors that automatically get executed when an object of the class goes out of scope. So if you allocate (or acquire) memory in your constructor, you can deallocate it in your destructor, and be guaranteed that the memory will be deallocated when the class object is destroyed (regardless of whether it goes out of scope, gets explicitly deleted, etc…).

Let’s modify our printDynamicInt example to encapsulate the int pointer inside a class object designed to own the memory we need:

This prints:

IntPtr allocated
IntPtr deleted

When i goes out of scope (whether by early return or at the end of the function), the destructor of IntPtr is called, which cleans up the dynamically allocated object. Once the IntPtr owns the memory, we no longer have to worry about deallocation not happening!

Key insight

Because the destructor of a class object is called when that object is destroyed, we can solve our dynamic memory ownership problem by making a class object the owner of our memory and using the destructor to ensure deallocation happen.

Warning

The above is only guaranteed if the class object isn’t dynamically allocated itself. If a dynamically allocated class object isn’t properly deleted itself, the destructor wouldn’t be called to clean up the memory. For this reason, classes that manage memory should generally not be allocated dynamically.

Introduction to smart pointers

// Update above examples with intro from https://www.learncpp.com/cpp-tutorial/15-1-intro-to-smart-pointers-move-semantics/

The IntPtr class above is a primitive (and flawed) example of a smart pointer. A smart pointer is a class that is designed to manage dynamically allocated memory and ensure that memory gets deleted when the smart pointer object goes out of scope. (Relatedly, built-in pointers are sometimes called “dumb pointers” because they can’t clean up after themselves).

The good news is that as of C++11, a variety of (non-flawed) smart pointers come as part of the standard library, so we don’t have to write our own.

While most programmers initially get into smart pointers as a way to handle dynamic allocation and deallocation automatically, smart pointers actually have a second, equally important benefit: they enforce ownership semantics, and can help make transfers of ownership explicit in function prototypes. Ownership semantics are rules that ensure logical and consistent ownership of a resource.

// needs a better definition of ownership semantics

In the next set of lessons in this chapter, we’ll talk about the smart pointers that the C++ standard library provides, and show you show they can be used to safely take advantage of dynamic memory allocation. We’ll cover the ownership semantics benefit in the next chapter, once we’ve covered some additional fundamentals (move semantics).


6.13 -- Void pointers
Index
10.3 -- Dynamically allocating arrays with new and delete

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">