In this video, we'll learn about the c++ copy constructor.
When objects are copied, c++ must be able to create a new
object from an existing object.
In order to do this, it uses something called a copy constructor.
There are several use cases where a copy constructor is used. For example,
if we pass an object by value to a function or method as a parameter,
then we need to make a copy of that object.
The same is true when we return an object by value from a function or method.
And the last common use case
is when we want to construct a new object based on an existing object of the same class.
C++ must provide a way to accomplish this copying.
So c++ plus uses a copy constructor.
We can provide our own copy constructor
and we can define exactly the semantics we need for object copying.
But if we don't provide a copy constructor,
the c++ plus compiler will provide a compiler generated one.
Let's look at some of the use cases in more detail
so you can see exactly when an object copy is required.
The first use case is when we pass an object to a function or method by value.
Remember, the default mode of passing data in c++ plus is by value,
which means a copy is made.
So in this example, we have hero which is a player object.
And then we have a display player function that expects a player object by value.
So when we call this function and pass in hero,
the function parameter p will be created and it will be created as a copy of hero,
using the copy constructor.
Notice that p is local to the display player function.
So when p goes out of scope, the destructor will be called as well.
In this use case we have an enemy object which is a player.
We also have a function create super enemy
that creates and returns a player object.
Notice that the player object and enemy is created in the function
and then returned from this function.
Since we're returning an enemy by value.
A copy of the enemy object is made by the copy constructor.
The last use case we'll look at is when we want to explicitly create a new object
from an existing object. In this example we have a hero object,
and we want to create another object called another hero based on hero.
In this case, a copy is made using the copy constructor.
So now you can see where copy constructors are used.
Now let's see what they do.
If you don't provide a copy constructor, c++ will generate
a default copy constructor that will do member-wise copy.
That means that it'll go through the class attributes and copy them from the source object
to the target object.
For attributes that are objects then their copy constructors will be called.
In many cases, this is perfectly fine and the default copy constructor is all you need.
But if you're using raw pointers, then only the pointer will be copied, not the data
that it's pointing to.
This is referred to as a shallow copy rather than a deep copy.
I'll defer shallow versus deep copying until the next video
where we'll look at a specific example.
So best practice with copy constructors are
always providing user-defined copy constructor
if you're using raw pointers.
Always implement the copy constructor with a const reference parameter.
Use the standard template library classes as member attributes
they already provide copy constructors.
Or you can avoid using raw pointers as data members if possible or use smart pointers.
So what does the method signature look like for a copy constructor?
The syntax might look strange at first but it's pretty straightforward.
First, it's a constructor, so it has the same name as the class.
And in the parameter list, we have a single object passed in of the same type.
We pass that object in as a reference and a constant.
Why by reference and constant? First, if we pass it in by value,
then we have to make a copy of it.
That's the whole point of the copy constructor.
We would defeat the purpose and end up with never ending recursive calls.
And as a constant because the semantics of copying means that
we're copying the source, we're not modifying it.
So the const prevents any modification to the source object.
The last two statements are the prototypes for the player
and account copy constructors.
Note that they're exactly the same except for the names of the classes.
So how do we actually implement the copy constructor.
In the next video, we'll look at the case where we have raw pointers as data members
and we'll do a deep copy.
In this video, we'll just concentrate on shallow copies.
In the body of the copy constructor,
we do whatever we need to initialize our new object.
Remember, we have access to source, so we can reference any attributes we want from source.
But rather than write assignment statements in the constructor body,
we can use a constructor initialization list as we've done before.
Here is the implemented player class copy constructor.
Notice that we're initializing the newly created object attributes
in the initializer list based on the source object's attributes.
Pretty simple and intuitive.
Of course, any code we write in the constructor body
will be executed immediately after the new object is initialized.
And here you can see the copy constructor for the account class.
Again, we're initializing the newly created object
based on the existing source account object.
Now let's head over to the IDE. We'll write a copy constructor for the player class
and step through the code so you can see exactly when it's called.
Okay. So I'm in the ide now,
and I'm in the section 13 workspace in the CopyConstructor project.
And what we've got here is that player class that we've been using all along.
I just made a couple of little changes to it.
I created three getters here,
and we're going to use those getters in a display function which we'll write in a little bit,
and they just return the name the health and the xp.
We've got our our single 3 arg constructor here with
default parameters as we've done before,
and it's implemented right down here using
our initializer.
Okay. So that's all we've got we've created four objects down here.
Now the first thing we want to do is
let's write the prototype for the copy constructor.
And it looks like the name of the class,
which is player in this case and a single object
in the parameter list.
And it's a const player by reference and.
We could just leave it like that or we can put source in here it doesn't really matter.
So that's it. That's all we need to say. Obviously, we need to implement that we'll do that in a minute.
And the other thing I want to do here is i want to write a destructor.
And basically, I just want to know when this thing is going to be destroyed
because it's going to be very important so that we understand
the life cycle of these objects.
So again, how do we write a destructor we put a tilde
in front of the destructor name, which is the name of the class.
No parameters. And in this case, I'm just simply saying
I want to output that the destructor
is being called
for and we'll just put a colon here.
And we'll do that for that name
that way we know which object is actually being destroyed.
Okay. That's it. So now we've got our destructor.So what we need to do now is
implement that copy constructor.
And I'm going to do it right down here right after our
constructor.
So what do we do? Well, we start off by writing player,
scope resolution operator player again,
and we're going to implement the same prototype basically right. So it's const,
player
by reference source.
And now what we can do is we can create the initializer list if we like.
And how we're going to initialize this? Well, I want to initialize the name attribute
to what
to this object's name attribute. In which case,it would be
source
.name.
I want to initialize the health attribute
to source.health.
Remember, I'm making a copy. So I'm copying these values over
and the xp to
source.xp.
That's it. Now we implement an empty body for that,
copy constructor.
And what we'll do is in the body, we'll write an output statement we'll say cout,
and we'll write something like copy constructor,
and we'll do something like made.
Let's see, we made a copy of
what we made a copy of source name,
and we'll put an endline there.
Okay. So that's it. That just tells us that
it'll tell us what we've made a copy of, That'll come in handy when we're stepping through this,
and we can understand what's going on. Okay. So now
we've got our copy constructor set up.
Now let's get -- let's create a function that expects a player by value
and we know that that's one of the use cases where the copy constructor is called. So let's do that.
Let's write just a regular function, and we'll call it display player,
and it will expect a player object, we'll call it p.
And remember, it's expecting it by value here.
That's the default way of passing parameters around. So this implies a copy.
And in here all we're going to do is I'll just copy the code that I've got over here. So I don't have to type it all in.
And what we'll do here is just
create those three output statements that displays
p.get, name p.can help, p.get xp. So we're just going to display those values for p.
Okay. Now I'm going to put some break points all over here
just so we can see what's being called.
We won't do it in the destructor because we'll see that being called it'll just
print out destructor called.
And what we've got now is we've got an empty object frank,
hero and villain which are all objects, and these are just regular constructor calls happening here.
So what we'll do down here is -- actually, we'll do it right here.
What we'll do now is we'll say something like display player,
I'll leave an extra line here,
we'll say display player, and we'll pass in empty.
Empty is an object so what's going to happen is a copy of that object will be made.
Okay. So what I'm going to do is. Again, I'll put a break point right there and let's
debug this and we'll step through it
and I'll move the output window just over there.
Okay. So let's step through this. And you can see that right now we're on line 26,
and that's because that's empty player object is being created here.
And we should see that print out that constructor right there.
So we've just three args constructor for none. Remember, that's the default none 0 0.
And we'll step through again and now we're just about to call this display
player function here.
We'll look at empty, which is the object we just created, and it contains
none for name 0 and for health and 0 for xp.
Okay. So we're just about to call this function right here.
Now before we pass any information in,
we have to make a copy of it, right. Because we're doing pass-by-value or pass-by-copy.
So I've got empty, which you can see right here.
I need to make a copy of empty, and that's what p is going to be.
Now p is local to this function. So again, it's a copy
of empty and it's on the stack, right.
So let's step through and what we should see is
we should see the copy constructor being called, because a we should see the copy constructor being called, because a
So let's step through.
And there you go. You can see we're right on line 31,
and we're in the copy constructor. What are we doing now?
We're making a copy of empty, and that copy is going to be called p.
So let's step through again.
We just did the initialization.
And now it's going to say copy constructor made copy of none.
That's exactly what we did. We made a copy of that
object whose name is none. Okay.
Now I'll step through one more time.
Now that p has been created. You can see p right here.
It's a local variable to this function. And it's none 0 0.
It's a copy of empty.
There are two different objects, but they have the same values right now.
Okay. Let's walk through this.
We just displayed the name, the health and the xp.
Now what should happen is
p is going out of scope right now, right. We're going to leave this function
so that stack frame needs to be cleaned up.
So the destructor for p should be called.
The destructor for the copy, not the destructor for empty,
but the destructor for that p copy. So let's do that.
And there you go, you can see the destructor has been called.
And now we're back in main on line 46.
And from this point on, we're just constructing objects as usual.
Okay. So hopefully that's clear. You can see it's it's these
little nuances, and using the debugger to step through things really, really makesthings
very nice to follow.
So let's do another example here. I'm going to stop the debugger here.
And what I'll do is we'll create
that empty. Well, you know what let's create a different -- Let's create a different empty.
In this case, let's just create something called xxxx
with 150.
That way we know what those values are.
And what I want to do here is I'm going to get rid of that break point right here.
I want to create another player object.
Okay. And I'm going to call it my new object,
and I'm going to create a copy of that empty object.
Okay. So again let's see what's going on here.
I've got empty is a player object,
and I'm setting the name to a bunch of x's, the health to 100, the xp to 50.
Then I'm creating another object, it's called my new object
and I'm creating it from empty.
So I want a copy of empty. This will call the copy constructor.
That's the whole idea of making a copy.
Okay. So let me put a break point there, and let's run this debugger through again.
Okay. So now we're here constructing that empty object right over here.
So let's just walk through that until we get to that other break point on line 44,
and that's where we're at now. So at this point, empty has been created.
If I look at empty up here, you can see I've got the bunch of x's, 150.
So now I'm right here on line 44.
This is a constructor right. I'm constructing or initializing or instantiating
the jargons all the same a my new object which is a player.
In this case, from this object that I've already created, that's where the copy comes in.
So when I step through this. I expect the copy constructor to be called again.
So let's do that.
And there we are. We're right in the copy constructor in the initializer,
and I'm going to walk through that.
It says right here I made a copy of that original xxx, 6x object.
I'll step through again.
And now we're right back to where we started before.
Okay. So hopefully that gives you some insight into these
copy constructors and how they work. They really make a lot of sense.
The syntax is a little wonky sometimes, but it's pretty straightforward.
Now one thing that I should mention is
right here we're using the initializer list
to initialize that new object.
We could change this if we like and just do something like
use a delegating constructor here,
right. So we could delegate source
.name source.health
and source.xp.
And I need a little colonel in front of that just like before.
So what we're doing there is we're doing the delegation.
And same exact thing should happen.
We'll run the debugger here and we'll walk through that real quick so you can see the delegation now happening
before we weren't delegating because we were just initializing right in place there, right.
So at this point, let's walk through it until we get to
line 45 this time, which is where we're at right.
Now so now I'm going to call the copy constructor again
to make a copy of this empty object we just created.So let's step through it.
Right here, I'm in the copy constructor.
But now rather than use this initializer,
I'm going to delegate the construction of this object to my constructor,
which is this guy right up here. So in this case, when I walk through it,
you can see transfer will control will transfer up to here on line 25.
And that's exactly where we are now. Now we're in the regular three arg constructor.
And it's going to three args constructor.
Remember, the first one was for empty. This is for the copy of empty.
So we've got the two objects alive now.
And when I'm done, I'm going to come back and it said made a copy of it.
Okay. So there you go.
There's the copy constructor, and you can see how we can delegate from that as well.