Hello again!
In this video, we are going to look at unique_ptrs.
unique_ptr is the best replacements in most situations.
If you have a traditional pointer which manages heap memory, the unique_ptr will allocate the memory
in its constructor, and it will have sole ownership of that memory.
It will automatically release the memory when the object goes out of scope.
So at the end of a scope, or when an exception is thrown. And this means that we can use memory which
is allocated on the heap, but the actual way that we interact with the object is like any other stack
object.
It has the usual advantages of smart pointers.
We do not need to worry about managing memory ourselves.
The pointer to the allocated memory cannot be overwritten or invalidated, and there are no issues with
shallow copying.
unique_ptr is a very lightweight class.
There is no more overhead to using a unique_ptr object than there is to using a traditional pointer.
The unique_ptr class is defined in the header.
It is a template class. It has a traditional pointer, which is a data member.
The public member functions of the class implement some, but not all of the operations that you can perform
on a traditional pointer.
The template parameter is the type of the data that the member is pointing to.
So if we have unique_ptr with parameter int, then the member is pointer to int. And unique_ptr is a
move-only class.
If we move a unique_ptr object, the allocated memory gets transferred from one object to another.
So that transfers the ownership of the memory.
To initialize a unique_ptr object. In C++11,
we have to call new ourselves, and then use the return value to initialize the unique_ptr. So
we can have something like that. So that it would be allocating memory for a single int object. And then having
a unique_ptr which manages that memory.
We could also have a fixed-size array, a we call new to create an array of ints.
Usually, though, it is better to use either a standard array or a standard vector.
The only situation where you would want to use this is if you need a fixed-size vector on the heap, because
the library array cannot be allocated on the heap, and a vector does not have fixed size.
It is possible to add or remove elements.
In C++14, there is a make_unique() function, which is a bit like make_pair(), or our make_test() function.
This will call new internally, to allocate the memory, and then it uses perfect forwarding to initialize
the data in the memory, from the arguments to the call to make_unique().
So we call make_unique().
We need to give the data type again.
And then the argument is 42.
So that is going to allocate memory for an int, and then it will use perfect forwarding to initialize that
int with the value 42.
If we use the array form, then this will allocate an array of six ints, and we are going to use default
initialization in this case.
So let's try that out.
So there is the C++11 form where we call new explicitly.
And then the C++14 version, where we call make_unique(), which will do that for us.
Okay.
So we can perform most of the operations on a unique_ptr that we can on a traditional pointer,
but not ones which involve copying, or modifying the pointer.
And also, the unique_ptr distinguishes between a pointer to a single element and a pointer to an
array. For example, we can dereference a pointer to a single object, but not a pointer to an array.
We can use index notation with a pointer to an array, but not with a pointer to a single object.
We cannot increment unique_ptrs, and we cannot assign them.
Or call the copy constructed.
We can move from one unique_ptr of the same type into another unique_ptr of
the same type.
But we cannot move from an array into a single object, or vice versa.
One other thing we can do, is to assign to null pointer.
This will cause the pointer member to be deleted.
It will release the memory, and then the pointer will have the value
null.
So that will release the memory, and make the object invalid.
Okay.
And that is a default initialized value!
As a very simple example of using a unique_ptr for a class or struct.
We can call make_unique().
We can pass an object of this class as the argument to make_unique().
So this is going to create a temporary "Point" object, and then allocate memory to store it, and then perfect
forward
this point object into the allocated memory.
We are using the auto keyword here, because that is a good place to use it.
We know that we have a unique_ptr to a Point structure, so we do not need to spell it out.
And then we can just dereference the members in this element, as we would with a normal pointer.
And there we are, three and six.
We can pass unique_ptr to a function, as an argument, by using pass by move.
So we write a function which takes unique_pointer as argument, and then we have to cast it to an
rvalue when we call it.
So that will force the move constructor to be called, and not the copy constructor, which is deleted.
So again, we have the same unique_ptr. And this time we pass it to the function. We call move() to
cast it to an rvalue.
And then this pointer will be moved into the function argument.
And then if you try to access the data in this pointer...
Well, there was a long pause there.
I think that was the program actually crashing, because we were dereferencing a null pointer.
So the pointer has been moved out of this object.
So if we try to dereference it, we are now dereferencing a null pointer.
We can also return a unique_ptr from a function.
So we just return unique_ptr, with the type again.
The object will be moved, from this local variable, into the function's return space. And then it will be
moved into the caller's object.
And by the way, we do not need to cast to an rvalue here, because the compiler will always move a return
value if it is possible.
So here we are, with our Point struct again.
And then in our function, we are creating a unique_ptr to an object with this Point.
And then we return it.
We just put the name of the variable, and not move().
In fact, if you do put a call to move in here, that can confuse the compiler, and it will generate code
which is less optimized than if you left it alone.
So the recommendation is just to return the variable here.
And there we are.
So we have called this function, point_ptr.
We passed the values three and six.
This function has created a unique_ptr, whose members have the values three and six.
And then we returned the pointer, and then we dereference that, to get the values of the members.
If we are not going to use this pointer again, then we can just return it.
directly.
And that still works.
Okay, so that is it for this video.
I will see you next time.
Until then, keep coding!