In this last section of the course, we'll talk about custom deleters for smart pointers.
This isn't something you're likely to see very often,
but I wanted to cover it for completeness since they are a feature that c++ smart pointers support.
Sometimes when we destroy a smart pointer,
we need more than to just destroy the object on the heap.
Sometimes when we use pointers to see structures from c frameworks,
we have to provide a specialized way of destroying them
since in many cases they don't have destructors.
As I said, these are special use cases that you won't run into very often.
But if you do, the c++ smart pointers allow you to provide custom deleters
that will be called when the smart pointer is destroyed.
If you use custom deleters, you can't use make shared or make unique
when you create your smart pointer objects
since we need to provide our custom deleter and these functions don't support that.
The idea is that if you need a specialized way to delete your objects,
you probably need a specialized way to create them too.
There are lots of ways to provide custom deleters.
I'll show you two ways in these slides. First, using a function and then using a lambda.
In the case of a function, we write our deleter function
that will be called automatically when the smart pointer will be deleted.
This function will be provided with a raw pointer
to the managed object that the smart pointer is referencing.
Then in this function you do whatever you need to do to relinquish your resources.
In this example, I'm simply deleting the raw pointer.
That's it. So now when you declare your smart pointer,
you provide your deleter function.
In this case, I'm declaring ptr
as a shared pointer to some class.
And in the initializer,
I'm creating the managed object using new
but then I'm also passing in the name of my custom deleter function.
That's it. Now when the pointer is destroyed, it will call my deleter.
Let's see a real code example using the test class that we've been using in this section.
So here we're creating a deleter function that will be passed in a raw pointer.
In this case, it's a raw pointer to a test object
and that pointer is the pointer that the smart pointer is managing.
In this function, I'm simply displaying in my custom deleter,
and then I'm deleting the raw pointer that was passed to me.
That's it.
Now when I create the smart pointer object, in this case,
ptr as a shared pointer, I pass in my deleter in the initialization list.
It's as simple as that. This will also work for unique pointers.
Now let's see how this works using a lambda expression.
We haven't seen lambda expressions in this course
since they're more of an intermediate c++ topic.
If there's interest in learning more about lambda expressions,
please let me know and I'll add a section to the course.
In a nutshell, a lambda is an anonymous function
that is a function that has no name and can be defined in line right where you expect to use it.
There's a lot more to lambdas, but you can see in this example
that the function name parameter has been replaced
with a code block that looks very similar to what we originally wrote in the my deleter function.
The syntax might look a little odd.
But now this code will execute whenever the pointer object is destroyed.
That's pretty cool.
We define right where we will use it and not need to write another function and have to worry about it.
Well, that completes the section on smart pointers.
I hope you can see how powerful smart pointers are.
There are many opinions as to what types of smart pointers you should use when.
And the answer is usually, it depends.
I would encourage you to use unique pointers as often as you can
since they're simple, efficient and in most cases
they're drop-ins for many of the common pointer use cases.
Then use shared pointers
when you have more complex object management semantics, involving shared ownership.
In the next video, we'll do a simple challenge that uses a unique pointer
to a vector of shared pointers. It should be fun.
Okay. So I'm back in the IDE. I'm in the section 17 workspace
in the CustomDeleters project.
And in this project what we'll do is we'll write a couple of
different ways to have our custom deleters being called.
And it's really, really straightforward let's do the function one first.
We've got the same test class that we've been using throughout this section.
You've seen it before, so it should be pretty easy to understand what's going on there.
And now what we want to do is we want to create our own deleter that will be called
when our objects are deleted. This is it right here.
This name can be anything you like. I just call it my deleter. It has to return void.
And what what's being passed into here is the raw pointer
that's being managed.
So in other words, if I've got a shared pointer to a test object on the heap,
we're going to get a pointer to that test, and it's going to be the raw pointer.
If this was an account,
we'd get a pointer to an account object, again, a raw pointer.
So that's the raw pointer. That's being wrapped by that shared pointer.
Okay. So what are we doing here. Well, this is going to be called with that pointer.
We can do whatever we like. In this case, I'm just going to say using my custom function deleter.
And then I'm actually going to delete that pointer, right.
I mean the whole point is for me to delete that storage.
Now this gets much more complicated when you've got raw pointers to c structures
that are used in c frameworks, which --
they don't have destructors. You've really got to manage that memory yourself.
And this is a nice way to do it and still be able to use unique pointers and shared pointers
and so forth.
So what do we do now. Well, now we can create our pointers.
It's called ptr1, and it's a shared pointer to a test object.
Remember, the test objects on the heap.
We have to use new here to do this. We can't use make shared.
And what we do is we pass in
the regular information that we're used to, right, to create that object on the heap.
And then we pass in the name of our deleter.
In this case, it's my deleter. You can see it's the same name.
You don't have to put address of in front.
You -- it doesn't hurt if you do, but that's not necessary.
The name of a function already defaults to the location of the function.
So in this case, what's going to happen behind the scenes is that when this pointer
is delete -- when this pointer is deleted and it has to clean up the memory,
it's going to call this function to do it.
And you can have complete control of how you do it.
So that's the function method. The second way is using a lambda expression.
And what you'll see here is you can see our output statement and our delete.
And you can see here our output statement our delete.
The only difference is that it's being wrapped up in this construct right here.
And this is called a lambda expression, which creates a closure object.
And what's going to happen is when it's time to delete this
rather than call a function,
this code right in here executes.
So this is real nice because you can write your code exactly where it's going to be used,
not somewhere else.
Obviously, this function could be anywhere.
But this is all right here. It's real nice. So when you're reading this,
you can see exactly what's going to happen, right. And it's right there,
rather than having to find a function to see what it does.
And that's it. There's two different ways to do it.
If we run this now. And actually, you know what -- let me change this to a 1000 here so we can see the difference.
So what's going to happen here is we're -- and notice I've created this block right here.
This is just an artificial scope here so that we can see
that this ptr-1 will be deleted right here, right. It'll be destroyed,
and ptr2 will be destroyed right here.
So we expect
ptr-1 to live inside this block and ptr2 inside this block,
that way you can really see them, and we can really see these deleters being called.
So let's execute this. And we'll build and run.
And you can see right here, let me move this over here just a little bit,
this is this block right here. You can see that
we're constructing that test object with 100.
And then we're using our custom function the leader, right. That's you can see that,
that output statement is displaying. We're deleting the pointer,
and then we're calling the destructor. Same thing here.
We're calling the test constructor with a 1000. Then we're using our custom lambda deleter,
which is this line right here. And we're destroying our pointer as well there,
and then we're calling the test destructor.
So there you go. Real simple example of using
custom deleters. You don't often need these,
but it's nice to know that they're there, and they're pretty easy to use actually.