fbpx

The C++ language guarantees that destructors run at specific times even if an exception is thrown. You can use this to make sure that other cleanup work gets done. That’s what smart pointers do.

A pointer can allow you to create new objects in some scope and then pass that object outside the scope that it was created in. What I mean is that you can have a method with all its local variables existing on the stack that will get destroyed when the method completes. But this method can allocate a new object on the heap, get a pointer to that new object, and pass that pointer back to the calling code. Ownership of the newly created object now belongs to the code that called the method and what it does with that pointer is up to that code. Maybe it sends the pointer and therefore ownership of the pointer to yet another method. It’s really important to have a clear understanding of what code is responsible for that pointer. And that’s what this episode will explain.

There are times when you want to make sure that only a single owner exists for a pointer. Just imagine the problems if both you and your neighbor thought you each owned the same car. In order to prevent this, C++ has a unique_ptr class that makes sure only a single piece of code owns a pointer at any time. You can transfer ownership but can’t have multiple owners.

And there are times when you do want multiple owners. In this case, you want to make sure that the owned object stays around until the last piece of code is done with it. This is like a shared driveway that you and your neighbor both maintain. Just because you decide that you don’t need a car anymore doesn’t mean that you can delete the driveway and plant vegetables there. The driveway needs to remain as long as either of you need it. C++ has a shared_ptr class to help with this situation.

Listen for more information about these as well as another type, a weak_ptr, that works with the shared_ptr. You can also read more in the full transcript below.

Transcript

This episode continues explaining smart pointers and how to use the smart pointers in the C++ standard library. There’s two main concepts to understand, exclusive ownership and shared ownership. But why is ownership an issue at all?

A pointer can allow you to create new objects in some scope and then pass that object outside the scope that it was created in. What I mean is that you can have a method with all its local variables existing on the stack that will get destroyed when the method completes. But this method can allocate a new object on the heap, get a pointer to that new object, and pass that pointer back to the calling code. Ownership of the newly created object now belongs to the code that called the method and what it does with that pointer is up to that code. Maybe it sends the pointer and therefore ownership of the pointer to yet another method. It’s really important to have a clear understanding of what code is responsible for that pointer. And that’s what this episode will explain.

There are times when you want to make sure that only a single owner exists for a pointer. Just imagine the problems if both you and your neighbor thought you each owned the same car. In order to prevent this, C++ has a unique_ptr class that makes sure only a single piece of code owns a pointer at any time. You can transfer ownership but can’t have multiple owners.

And there are times when you do want multiple owners. In this case, you want to make sure that the owned object stays around until the last piece of code is done with it. This is like a shared driveway that you and your neighbor both maintain. Just because you decide that you don’t need a car anymore doesn’t mean that you can delete the driveway and plant vegetables there. The driveway needs to remain as long as either of you need it. C++ has a shared_ptr class to help with this situation.

Let’s look at the exclusive ownership first and start with a simple example of allocating an object and making sure it gets deleted when done. Without smart pointers, if you wanted to allocate a new int, you’d first declare a pointer to an int as a local variable and assign it the value you get from calling new int.

That gives you an int pointer and you can start using it. At the end of your method, you need to call delete on your pointer variable. But what happens if you want to return early from your method? Well, you can and you just need to remember to call delete right before returning early. What if the method is a bit complicated and you have multiple places where it can return? Again, you can as long as you delete the int pointer each time right before returning.

All this extra deleting just adds more code to your method. And it doesn’t help if an exception is thrown. So to handle exceptions, maybe you try adding a try block and then a catch all block. In the catch all block, you also delete the int pointer.

There’s a much better, safer, and easier way. It’s not often that you get such a good deal. Normally, you have to give up something. About the biggest thing you have to give up here though is the possibility of doing something wrong. There’s really no good reason not to use a smart pointer.

All you have to do is declare a unique_ptr of type int and initialize it with the value you get from calling new int. This is almost identical to how you created the raw int pointer before. There’s a technical name for this called RAII which stands for resource allocation is initialization. All it really means is that anytime you think about allocating some resource, use that to initialize a smart pointer. It’s a bad name for some really good advice.

Once you have your unique_ptr, you can use it like normal. But the best part is that you no longer have to call delete. Anywhere. Not at the end of your method. Not if you need to return early. And not even if an exception is thrown. Just let the smart pointer do its job and it will make sure to call delete at the right time.

This is not the same thing as garbage collection in C#. You don’t just forget about the new object and let the garbage collector run at some unspecified future time. You know with a smart pointer exactly when the object will get deleted and that happens the moment the smart pointer goes out of scope.

Unless you take steps to prevent this. You see, even if you want to eventually pass a newly created object outside of a method, you can still use a smart pointer to protect your code from a memory leak in case some error prevents the method from working properly. As long as the method runs fine, then you can pass a smart pointer out of the method just like a raw pointer.

About the only thing you give up with a unique_ptr is the ability to assign a new value easily. You can still do it, but the unique_ptr makes it so you have to write a bit extra code to do that. Just enough so you’re not going to do it on accident. That’s a good thing because it makes sure that you know the previous object is getting destroyed and a new object is taking its place.

I’ll explain shared pointers right after this message from our sponsor. They’re a bit different than unique pointers and come with another type called a weak pointer.

In the previous example, I explained how a smart pointer such as a unique_ptr can help protect a newly allocated object to make sure it gets deleted when you’re done with it. That’s a much more straightforward example. What if you want a single object to be used in multiple places? Now it becomes much more difficult to determine when you’re done with the newly created object. Because each code that needs to use the object has no idea if it should be deleted or not when it’s done. All that code knows is when it’s done using the object. It doesn’t know if some other code could still be using it or not. This adds a whole new requirement to managing resources.

The shared_ptr class allows you to initialize it with the result of calling new. If you need to share an int pointer in multiple places, then declare a shared pointer of type int and initialize it withe the value from calling new int.

You might wonder why you would ever want multiple places in your code to all refer to the same object? It’s up to you, really, if this is what you want or not. Sometimes you want separate copies which means that if you change one, it has no effect on the other places. but other times, you want to have a single master copy of an object so that if you change it there, then that change appears in other places in your code. This is where you can use a shared_ptr.

Just like the unique_ptr, you give up almost nothing to be able to do this. Using a shared_ptr though does need a little more memory because it needs to keep track of how many places are still using it. You still can’t, for example, create and initialize a shared_ptr and then just assign a new raw pointer to it. This can be done, but you just have to write a little more code to make sure it doesn’t happen by mistake.

Every time you want to pass a shared_ptr to another piece of code, just create a new shared_ptr of the same type and initialize it with the current shared_ptr. What this does is increase a reference count. When you’re done with the current shared_ptr and it goes out of scope, then it’s destructor will be called which doesn’t immediately delete the resource. The shared_ptr destructor first decrements the reference count to show that there’s one less shared_ptr using the resource. Only when the resource count goes to zero does the shared_ptr know that it was the last one using the resource and it’s now safe to delete it.

This works great most of the time. But what do you do when you have a cyclic dependency? This is when, for example, object A refers to object B and object B also refers back to object A. If these two objects were using shared pointers, then they would never get deleted because their reference count would never get to zero. That’s because they each have a hold on the other.

In a garbage collected language like C#, this is not a problem. Because while C# does keep track of how many times something is being used, it does so by examining everything currently within scope and accessible to your code. In C#, if you have two objects that each refer to each other and you forget about both of them, then they’ll still be deleted whenever the garbage collector eventually runs. That’s because it’s able to determine that even though they each refer to each other, if there’s no place in your code that refers to them, then they might as well be deleted.

C++ doesn’t have a garbage collector and can only look at reference counts. The way you solve this situation in C++ is to make one of the smart pointers be a weak pointer.

You see, a weak pointer gives you the ability to participate in a shared_ptr without actually taking part in the reference counting right away. When you want to use a weak pointer, you first need to convert it to a shared_ptr. This will work if the shared_ptr is still valid and hasn’t been deleted yet. Once you have your shared_ptr, then you’re safe to keep using it as long as you need. When you’re done with it, let it go out of scope and you’ll still have your weak pointer that you can use again if needed.