Let's start by exploring c++11 smart pointers with the unique pointer.
The unique pointer is a simple smart pointer that's very efficient,
and it can be a drop in replacement in many cases
for your typical pointer use case
where you allocate storage use it and then free in the same block.
A unique pointer declaration uses a template parameter
which is the type of the object that the smart pointer will manage on the heap.
So this allows us to declare unique pointer objects
that point to any type we need.
As the name states, unique pointers are unique,
so you can't have multiple unique pointers pointing to the same object on the heap.
This implies a strong ownership relationship between the pointer
and what it points to.
Unique pointers can't be copied or assigned.
So their copy constructors and copy assignment operators are not available.
If you try to copy or assign them, you'll get an error.
But unique pointers can be moved,
so they're great for placing in standard template library containers such as vectors.
And since the ownership is unique,
you're guaranteed that when the pointer goes out of scope,
the object that it points to will automatically be cleaned up from the heap.
That's pretty powerful stuff.
So let's see one way we can create a unique pointer.
Here you can see that we're declaring p1 to be a unique pointer to an int.
We're also initializing it to point to a new int that we create on the heap
and that integer has been initialized to a 100.
Now p1 owns that object on the heap.
When p1 goes out of scope, the int on the heap will be de-allocated.
You don't need to call delete with the smart pointer.
Now as far as using the pointer, the syntax is pretty much like using a raw pointer.
You can see that if we de-reference p1, we get the integer that it points to,
in this case, 100 and we display it.
We can also modify the integer we're pointing to in a similar manner.
There's another better way to initialize a unique pointer, and I'll show you that in a little bit.
It's also possible to create a raw pointer variable and then initialize the
unique pointer variable using that raw pointer.
I'm not going to describe that since it's not good practice to do
since you're referring to an object on the heap from a smart pointer and from a raw pointer,
not something you typically want.
The unique pointer has many methods, but here are some useful ones.
In this case, we again have a unique pointer p1
that points to the integer 100 on the heap.
The get method returns a pointer to the manage object.
You won't often need to use this method,
but it can be useful when you're working with libraries that use raw pointers.
The reset method sets the pointer to null pointer and the memory
it points to will be released.
Also notice that we can check if a pointer is initialized
using a simple if statement as in if p1.
In this example,
the if block won't execute since we just reset the pointer to null pointer.
We can also compare unique pointers using relational operators.
Of course, unique pointers as well as other smart pointer types
allow us to point to user-defined types. In this example,
we're creating a unique pointer that will point to an account object,
and we initialize it to point to the Larry account.
Notice the syntax is as you would expect.
We use a count as the template argument in the unique pointer declaration.
What this says is that the unique pointer p1
will manage an account object on the heap.
And we're creating that account object in the initializer list.
Now we can use this pointer as we would use a raw pointer.
We can de-reference to get the account object
as well as access the account object member methods via the member selection
or error operator.
And again, when p1 goes out of scope,
the heap storage associated with the account object is de-allocated.
As we said, unique pointers can't be copied or assigned,
in this example, we create a vector that will contain unique pointers to integers,
and we also create a pointer, which is a unique pointer to an int,
and it points to the integer on the heap, which has been initialized to 100.
We can't simply push back that unique pointer to the vector
because that implies a copy of the object.
And since unique pointers can't be copied, this will give you a compiler error.
However, we can use move semantics to move the pointer.
In this case, ptr gives up ownership of the pointer, and it's moved to the vector.
The vector now owns the pointer, and ptr will be set to null pointer.
When the vector goes out of scope, it will de-allocate all its elements,
and the integer on the heap will be de-allocated correctly.
Now let's see another better way to initialize unique pointers.
Since c++14, we can use the make unique function.
This function returns a unique pointer of the specified type,
and it allows us to pass initialization values
into the constructor for the manage object.
So in the first example,
we declare p1 as a unique pointer to an integer
and use make unique to initialize the pointer
and create the managed object all in one step.
Notice that in this case, we're not using the new keyword.
In the second example, p2 is a unique pointer to an account object.
And again, we use make unique and provide the template argument account
as well as the initialization values, which will be used by the account constructor.
Again, in this case, we don't use the new keyword.
The last example works the same way except we're using the auto keyword
to let the compiler deduce the type of p3
based on what make unique returns,
in this case, a unique pointer to a player object.
The auto keyword can be very useful here
since it makes the code more readable and more writeable.
Now let's head over to the IDE and go through some examples of using unique
pointers in live code.
Okay. So I'm back in the IDE. I'm in the section 17 workspace
in the UniquePointers project.
Now there's a lot of stuff going on here, but let's start it out slow. You can see that
in the source tree here, we have our account hierarchy,
including our printable interface.
So this is the one that's using dynamic polymorphism
with that abstract base class account.
We're going to use thunique pointers in a vector.
But before we get there, let's just play around with a real simple
class called test that I wrote right here, it starts on line 12.
You can see that I've got a class test,
and it's got an integer
as an instance variable. It's got a couple of constructors, one just initializes
data to 0. That's the no args constructor.
And the other constructor just initializes data to whatever is passed in.
I've got a data method that returns that data and a d structure
that just says hi I'm a destructor, real simple.
So let's create a test object just to be sure that this works okay.
So let's create a test object called t1,
and we'll initialize it to a 1000.
Now when we run this, because it's on the stack,
we should get the constructor and the destructor being called.
And you can see that's exactly what's happening here.
Test constructor with a 1000, test destructor with a 1000. Perfect.
Now let's do it with a pointer.
And what we'll do is we'll just modify this slightly.
We'll say parens t1 is a new test.
And we'll still use the 1000.
Now obviously, if i don't delete it, I'm going to leak memory.
So you can see that's exactly what happened here, right.
My test constructor was called, but the destructor was never called.
So as we've already learned what we have to do is we have to say delete t1.
And now when we run,
we'll have both the constructor and the destructor called, and we're not going to leak any memory.
That's exactly what we want. This is using raw pointers. What we want to do now is use
different kinds of smart pointers in the next couple of videos.
In this video, we're using a unique pointer. So I'm going to comment this code out.
And we'll do the same thing using a unique pointer.
So the first thing we want to do is
we want to declare the unique pointer.
Remember -- so we're going to say std unique pointer.
And now we need a template parameter. This is the type of class that we want
to reference. So in this case, it's test.
Later on, we're going to use a count, but it can really be any class you want.
And what do we want to call our pointer. Let's call it t1.
And now we'll use the new way of creating it. We'll say new test,
and give it the constructor parameter.
All right. So what's that going to do. Let's walk through this one.
We're creating a variable called t1, which is a unique pointer to a test object.
And we're instantiating that test object right here and initializing it to 100.
So in this case, we've got that unique pointer pointing to this heap object right here.
Notice there's no delete t1 necessary.
What's going to happen here is the constructor will be called
and then the destructor will be called automatically,
like we've seen so let's run that and you can see right here
test constructor 400,
test destructor 400, we never call delete.
We did call new because that's the style that we used to run that one.
So now let's use the make unique function to do the same thing. So we'll say, again, std.
And again, we're going to point to a test object. We'll call this t2,
and then we'll simply say equals std
make unique.
And the first thing we want to do is we want to pass in the template argument, which is test
followed by whatever constructor data we want to pass in.
So let's say this is a 1000.
Okay. Now this syntax might look a little odd, but it really is pretty straightforward.
Again, I'm creating a unique pointer to a test. It's called t2,
and I'm using the make unique function to do it.
Well, the make unique function can make any kind of unique pointer.
So we have to tell it what kind of unique pointer we want, and that's what we're doing right here.
Once we do that,
the piece here inside the parens we're here the 1000,
that is the argument that you want to pass into the constructor,
that's going to call this constructor right here on line 17.
So now what we'll have is we'll have two unique pointers, both on the stack,
both will be constructed. And when they're when they go out of scope,
the heap memory that they point to will be destroyed automatically. So we don't have to call delete.
And let's run that, and you can see here test constructor for a 100,
for a 1000, and then we're destroying both of them. So that's pretty cool.
We really had nothing to do in this case right here on line 28
with the make unique function. We didn't even have to use the new keyword at all.
And that's one of the things that you'll hear about modern c++.
You don't have to use new. You don't have to use delete.
You can just use smart pointers, and they handle it all.
And for the most part, that's true, but there are cases where you still have to use new.
Okay. So that's a good example right here. Now let's do another example.
Suppose that I want to create another unique pointer,
and we'll call this one t3,
and it will also be to a test.
Let's just call it t3, just like that.
And it's initialized. Remember, this is an object so it's going to call a constructor for the unique pointer.
It's just not pointing anywhere now.
So now what do we do. Can we say t3 equals t1?
Well, I can't do that because I'm not allowed to assign unique pointers.
And you can see the error right here, use of the deleted function.
It's a long-winded error basically telling you that that operator -- you kind of see
it over here in the error message. It's kind of telling you that that operator equal has been deleted.
You don't have access to it.
Same thing if you tried to copy construct,
it won't let you because you're not allowed to copy or assign unique pointers.
But you can move them. So what we can do is we can say std
move t1.
And what that's going to do it's going to move the ownership of that test object on the heap
from t1 to t3.
So now t3 will be referring to it owning it,
and t1 will have a null pointer in it. So we can run this.
And you can see everything works exactly the same.
And we can actually test right here. We could say if
not t1,
right, so if t1 is not pointing anywhere -- in other words, if t1 is a null pointer,
then we could just simply say cout
and just something like t1 is null pointer.
It's the only way that this is possible, right.
And we'll end that line here, and we should see that display.
Oops, let me put that paren back in.
And there you go. You can see the output right there. It says t1 is null pointer.
That's exactly what we expect, right, because when you move the ownership
from t1 to t3,
t3 now owns that object. T1 is now null.
It can't own the object because we're not sharing ownership.
Now let's create some unique pointers to accounts.
And the way we can do that is really, really straightforward again.
And let me let me comment this all out so it doesn't all keep displaying.
And what we could say is we could say that we want a unique pointer
to an account.
Now remember, this is a base class pointer now, which is what we want
because we want dynamic polymorphism.
And what do we want to call it, let's call it a1.
And we'll use make unique.
So now what are we pointing to, we can't say account
because account is an abstract base class.
So we have to have one of those types of account, one of our concrete classes.
So in this case, let's say we want to point to a checking account.
And now what are the constructor parameters for a checking account.
What we can say Moe and let's say 5000 dollars,
and that should do it.
At this point, we can use that object however we want, right,
but we have to get to the object. So if I want to display that object,
I can simply say de-reference a1. Remember, a1 is a pointer.
So if I de-reference a1, I get that checking account object,
and it's going to call our overloaded insertion operator that we've defined.
So let's see if it does. Let's get rid of that extra paren right here,
and let's give this a run.
And there you go. Checking account Moe 5000 dollars, pretty cool.
Of course, we can use the object in any which way we want.
So we can say a1, and we could say deposit
another 5000 dollars.
And let's display the object again.
So you can see we're using -- we're de-referencing, and we're using the arrow operator here as well
to access the actual object that we're pointing to.
So now we can build and run.
And you can see that Moe was 5000, and now Moe is 10000.
All right. Pretty cool.
Now let's do a couple of more. Let's create a vector.
And what we can do here is we can create a vector.
And that vector is going to be a vector of unique pointers.
And those unique pointers will point to account objects.
Okay. So again, it's a vector of unique pointers.
And those unique pointers are going to be referencing account objects.
And we could just call this accounts,
simple as that. And now what we can do is we can create some account objects,
right. But in this case, we're going to create them on the heap because we've got pointers to them.
And we're going to create a savings, a checking and a trust.
And I'll copy that code over just to save a little typing time.
Okay. So I copied it over. You can see what I'm doing. I'm adding
checking account James with a 1000 balance
to the accounts vector. And I'm doing that using the pushback method.
And I'm also using make unique. Because remember,
this is a vector of unique pointers.
So I'm adding James Billy and Bobby, a checking, a savings and a trust account.
So that's pretty easy.
Now all we need to do is loop through that vector
and display those account objects, right.
So let's do that. We'll say for,
and we could -- we can use a range-based for loop here. And I can say for auto,
and let's just call that acc in accounts, which is my collection.
And at this point, all we really need to do is just say
std cout,
right, de-reference acc, which is my account
and display a new line. Now this won't compile. When I try to compile this,
I'm going to get an error right here. And the problem is this is doing copy,
right. We're copying each of those unique pointers that's in the account,
vector. But we can't we're not allowed to copy unique pointers.
So we have to do this by reference.
And it's even better if we do it by const reference.
So now we can run our program.
And you'll see that we're displaying
James Billy and Bobby's accounts.
Okay. And notice that we didn't do a single delete here, right.
Memories being cleared up automatically for us.
If we really wanted to see those destructors called,
we can go into our account classes and just change that
destructor from the default instructor to actually just put some code in there that says
hi I'm being destructed or something like that,
but they are being called, and that's pretty cool.
In the next video what we'll do is we'll learn about shared pointers.