Now that we know what shallow copying is in the context of the copy constructor,
let's look at deep copying.
With a deep copy, we don't just simply copy the pointer.
Instead, we copy the data pointed to by the pointer.
This usually means that we have to allocate storage for the copied data and then perform the copy.
With a deep copy, each object will have a pointer to unique
storage in the heap and both areas will contain the same data.
You always want to use a copy constructor that does a deep copy when you have
raw c++ pointers.
Let's look at the class we'll use in this example.
It's exactly the class we use for the shallow copy
except that I named this class deep to be clear.
We have a raw c++ pointer that points to a single integer, and here's the class we'll use.
This class has a constructor that expects an integer a copy constructor and a destructor as before.
And let's see how we implemented these methods.
Here's the constructor, and you can see that this code is exactly the same as we implemented
in shallow. After all, we want to create those objects the same way.
The constructor allocates storage for the integer and then stores the passed in integer d
into where that data pointer is pointing.
Since we allocated the storage dynamically,
then we have to release it in the destructor, so we'll do that now.
Here's the code for the destructor. Again, it frees the data that was allocated by the
constructor and displays a message to the console.
Now this is where the change comes in. It's with the copy constructor. Let's see the difference.
So here's the copy constructor.
And this is where things change from the copy constructor we saw previously that does a shallow copy.
Here we don't simply copy the pointer.
Instead, we allocate storage for the copy of what we're pointing to
and then copy the data itself over. So you can see we're creating a new integer
and then we're copying what the source data pointer is pointing to which is an integer
into our newly created area on the heap,
and we're also displaying a message.
Now we have an exact unique copy of the original heap storage in each object.
Since we can also delegate object construction from a
copy constructor to another constructor within the same class,
we could do that as follows.
Notice that we're delegating to the constructor that expects an integer.
And the integer we're passing in is the one pointed to by data.
Now we've made a deep copy of that data pointer.
And both source and the newly created object each have an exact unique
copy of the original heap storage.
So now we'll have that function display deep that expects a deep object by value.
We make a copy s, use it. And then when s goes out of scope,
we call the destructor which frees up the allocated storage.
In this case there is no problem since the allocated storage
pointed to by the data pointer and s is unique to s.
And finally, here's the main that we use for the shallow example
except we're using it now with the deep example.
This code will now run correctly.
There won't be any crashes and no problems
because the copy constructor is doing a deep copy,
and we're responsibly handling memory.
Now let's head over to the IDE, and we'll walk through this example,
and I'll draw out again exactly what's happening with the deep copy.
Okay. So I'm back in the IDE. I'm in the section 13 workspace
and I'm in the DeepCopy project.
In this case we're using a class called deep.
It's exactly like the class called shallow that we used in the last video with the exception
of the copy constructor.
In this case, you can see the copy constructor right here on line 26,
and it's doing a deep copy by delegating to our regular constructor.
That's really the only difference is we're not just copying the pointer we're copying the data
pointed to by the pointer.
So let's walk through this example using the debugger.
And let me move that output window over to the right.
And right now we're on line 42, so we're just about to create object1.
Now this is just a regular simple instantiation with the constructor here on line
21. So let's step through it.
We can see now we're on line 22. We're creating that integer on the heap
and we're storing the 100 into it.
And now we're back. So object1 at this point has been created.
And you can see object1 right here.
You can see data is a pointer to an integer and the integer it's pointing to is 100.
And you can see the address that we're pointing to is foxtrot 98.
So this is where we're at right now.
We've got object1 right here. It's got a pointer called data,
and it's pointing to an integer on the heap, that's 100.
And its address is ending with foxtrot 98.
That's the situation now.
So the next thing we want to do is we want to call display deep.
Display deep is this function right here. Again, this is a regular function, not a member function.
We're passing in a deep object into it by value.
So we need to make a copy of it.
So when we're in this function s will be a copy of object1,
which is what we're passing in right here. In order to get the copy,
we need to execute the copy constructor. So let's do that right now.
You can see the control transfers right here into the copy constructor,
and then the copy constructor is going to delegate construction to our regular constructor
that just expects a single integer. And that's what's happening here.
So now we're creating storage, and we're copying that data into it.
And now we come back to our copy constructor, we display our message
and we're back now on line 37. So s has been
created as a copy of object.
Now let's take a look at s. You can see s right up here,
and you can see we've got a data pointer and
pointing to a 100. So at this point
what's really going on is s right here is an object
and it's pointing to an integer.
Obviously, it's using its data pointer to do that,
and that integer contains -- you can see right here contains 100
and it is an address
foxtrot baker 8.
So we've got two different areas in the heap.
So each one of these is pointing to a unique area.
This is really what deep copy is all about because what's going to happen in a second
is when we finish this function right here,
we need to clean up s, right. It's going out of scope.
So the destructor for s is going to get called and it's going to free up this area in memory.
It's not messing with this area.
So when we come back from this function and come back to main,
object1 is still pointing to valid data.
This is very, very different from the example we saw with shallow.
So let's run through this and we'll write here,
you can see we're just about to go out of scope on line 38.
And there it is. Right there the destructor is being called on line 32.
The destructor is going to free up this object right here,
which is foxtrot bakery, right here.
We'll execute that line of code.
My destructor just freed the data.
And now I'm right back to main on line 45.
So at this point, this has been taken off the stack. It's all been cleaned up.
And this is the state that we're in right now, which is exactly what we expect.
Okay. Same is true on line 45.
Here what we're doing is we're making object2,
a copy of object1.
So again, we expect the copy constructor to be called here.
So let's walk through this. There it is. There's the call.
We're going to delegate construction to our regular constructor,
which happens here. My data gets written over, and
I'm back. Now at this point, you can see that object1
and object2, both contain a 100.
But you can see the addresses they're at.
They're completely different memory addresses for that 100.
So in other words, object1 has a copy of a 100 in its own area.
And object2 has a copy of 1000 in its own area.
And in this case here, on line 47,
if I set that integer value for object2 to a 1000,
and let me do that right now, you can see when I refresh
that object2's data
is a 1000 but objects1's data is still 100, again,
really different from what we had with the shallow copy
since they were both pointing to the same place.
So that's it. At this point 1 line. So we
free up object1 and object2.
And our program completes with no crashes at all.
Okay. So that gives you a
little bit of a perspective with shallow copies and deep copies.
Later on, we'll do this with smart pointers,
but it's really important to understand exactly what's going on with raw pointers first.