In this video, we'll learn how copy and move constructors and overloaded
assignment work with inheritance.
As we've seen, copy and move constructors and the
copy and move assignment operators are not automatically
inherited from the base class.
First, let's remember that you may not have to provide
these operations at all.
Depending on your class and its composition, the compiler provided
versions may be all you need.
However, we can explicitly invoke these special
constructors and the overloaded assignment operators in our base class from
the derived class if necessary.
First, let's look at the copy constructor.
If we need to copy a derived object, then we must ensure that the base part
of that derived object is also copied.
If that base class has a copy constructor, then we can explicitly
invoke it in our derived copy constructor's initialization list.
This is very similar to what we did with our non-special
constructors in the previous video.
The only part that might be confusing is that the base class
constructor expects a reference to a base object, but we have a
derived object in the derived class.
In this case, the derived object is referenced by the variable other.
So we can invoke the base class and pass in other, how does that work?
Well, remember we have is a inheritance.
So a derived is a base.
And since a derived is a base, we can pass in a drive to a
method that expects a base.
In this case, the compiler uses slicing and slices out the base
part of the derived object.
Slicing can sometimes cause issues, but in this case, it's fine since
what we want to do is copy the base part of the derived object.
Let's see the entire process in context.
In this example, we have our base class, and it has an
integer attribute named value.
And let's just focus on the copy constructor.
We construct a new object based on the other object, and we simply copy
other.value to this dot value and display a message to the console.
You may be wondering why we need a copy constructor at all
in this example, and you'd be absolutely correct, we don't.
The compiler provided copy constructor would be perfectly appropriate here.
However, for instructional purposes, I'm writing the copy
constructor so that we can see exactly what's being called.
Okay, so now let's see the derived class.
Here's the derived class.
Notice that it's derived publicly from base, and it adds an integer
attribute name doubled value.
The derived class has its own copy constructor that expects a ref to a
derived object that will be copied.
In this case, we're explicitly copying the base part of other
by invoking the base class copy constructor in the derived copy
constructor's initialization list.
Again, notice that we pass the other object, which will be
sliced to yield its base part.
Once we copy the base part, we can take care of copying double value and
then display a message to the console.
The move constructor works the same way.
Now let's see how overloaded copy assignment works.
Here's our base class again, and it has an overloaded
copy assignment operator.
In this method, we check for self-assignment, and then we assign
the right-hand side's values to this object, and we return this object.
This is exactly what we've learned already and done previously.
Now let's see the derived class.
Here is our derived class, which is publicly derived from base.
As before, it adds a double value attribute.
It also has an overloaded copy assignment operator.
In this method, it's important that we assign the base part
of the derived object and then take care of the doubled value.
So after we check for self-assignment, we call our base classes
overloaded assignment operator and pass in the right-hand side
object that we want to assign.
This will take care of assigning the base part.
Then we simply set double value to the right-hand sides
double value to complete the assignment of the derived part.
Finally, we just return to this object.
Note that if we don't explicitly call the base classes overloaded
assignment operator, then the base part will not be assigned.
Again, the move assignment works in a similar manner.
Okay, so let's recap.
Often you don't need to provide your own copy, move constructors
or overloaded assignment operators.
If you do not define them in the derived class, then the compiler
will create them automatically and call the base class versions for you.
If you do provide them in the derived class, then you must invoke the
base versions explicitly yourself.
Finally, be careful when you use raw pointers and dynamically
allocated memory, especially if both the base and the derived
classes have raw pointers.
If so, you'll likely want deep copy semantics for both
the base and derived classes.
So let's head over to the IDE, and we'll step through this example.
Okay, so I'm back in the IDE.
I'm in the section 15 workspace in the CopyConstructorAssignmentOperator project
Now this project is very similar to what we've seen before.
I'll scroll up here, and you can see we have a base class that has an
integer attribute called value and then we've got a no args constructor
an overloaded integer constructor, a copy constructor here and an
overloaded copy assignment operator.
And these are exactly like we've seen before, and we also have a destructor,
of course, down here at the bottom.
What we're doing here is when we get no args, we'll set value to 0.
When we get an integer, we'll set value to whatever integer we get.
In this case, here's my copy constructor.
Again, the compiler provided one would have been great but I
want to make sure that you see how these things are called.
So in this case, I'm just copying the others value to this value,
and the same thing with the overloaded copy assignment operator.
I'm checking that we don't have self-assignment, and I'm
just assigning the right-hand sides value to this value.
Okay, great.
Now let me close down this derive so we'll get to the
derived in a little bit.
So if we run this, we've got -- here's a real simple main.
And I've got a break point right on line 65.
The first thing we want to do is we want to create a b
object, and we're providing an integer as an initializer.
What we expect there is that the overloaded integer constructor fires,
and that's exactly what will happen.
Then we've got a copy constructor because we're
creating a b1 based on a b.
And then finally, an assignment operator.
And this is copy assignment here.
This is an l-value.
Okay, so let's walk through this, and this should be pretty much
review from what we've done before.
I just want to make sure that you understand the base part
before we do the derived part.
All right, so I'm right here on line 65, I'm just about to
create that object right here b.
So let's step through this, and we expect the base overloaded
integer constructor to be called, and that's exactly where
we are right here on line 16.
We'll set the value to 100, which is what x is right here,
and we'll display int base constructor, and that's it.
We've just constructed this object b.
Let's take a look at it real quick, you can see its value is 100.
And that's a base class object.
Now we're going to construct b1, which is also a base
class object based on b.
So we're going to call the copy constructor.
And you can see the copy constructor right here on line 19.
So let's do that, and I double clicked.
But you'll see what I'm in here we're in base, and we set this
value to the others value, and we just -- we're going to displace base
copy constructor here in a second.
So let me execute that, and that's it.
So now you can see that b and b1 are indeed copies of one
another, and we use the base copy constructor to do that.
Finally, we're going to say b equals b1.
In this case, b already exists.
So this is not a constructor, this is copy assignment.
So we expect this method right here on line 24 to be called because
that's my copy assignment operator.
So I'll step through this.
There it's called.
We'll just say base operator equals being called.
It's not self-assignment, and I'm simply assigning the right
hands value to this value, and returning the object, and I'm done.
Okay, so you can see exactly what's happened, exactly
what we expected, right.
Now we're going to unwind because these objects are going out of scope.
So we expect b1 to be destroyed first, and then b to be destroyed second.
So the first destructor is the one for b1, and we'll execute that.
There you can see the output right here.
And then the destructor for the b object executes,
and our program is done.
Okay, so now let me comment these lines out.
And what we'll do is we'll uncomment these lines, and we'll
go through this code in a sec.
I'm going to move that break point down to line 69.
And let's take a look at our derived class right here, I'll expand it.
So here we have a derived class that is derived publicly from base.
It adds its own attribute doubled value.
Remember, it gets value from base, but we've added double value.
And here I've got a no args constructor, and it invokes the base
classes, no args constructor because we want to make sure that the --
I mean this would happen automatically, but I'm just doing explicitly here.
Then we've got the overloaded constructor for the drive.
Then what we do here is we call the overloaded base constructor
for an int right here.
And when that's done, then we take care of initializing our
own stuff, which in this case is doubled value, which we simply
initialize it to 2 times x.
And then we've got our copy constructor right here.
It expects a derived.
And what we do here is we call our base classes copy
constructor, and we pass other.
Other is a derived.
Remember, base expects a base, but since a derived is a base, we
can certainly pass other in here.
And when we go through the debugger in this case, I'll show
you the slicing and how it works.
Then we initialize our part, and we're done.
And then finally, we've got the assignment operator.
The copy assignment operator where we check for self-assignment.
And this line is really important here.
What I'm doing here is we're calling our base classes overloaded
assignment operator, and we're passing in the right-hand side.
Again, that method in our base class expects a base object here,
right a ref to a base object.
What we're doing here is we're passing in a derived.
So the same idea we're going to get slicing there.
Then once the base is assigned, I take care of assigning the part
of the derived that really this method is only concerned about,
and we return this, and we're done.
Okay so let's take a look at the main real quick.
Here we are -- it's very similar to the example with the base.
Here we're creating d, which is a derived class object.
And we're using the initializer the integer initializer, so it'll
so it'll call the overloaded constructor.
Then we're copy constructing d1 from d.
And then finally, we're going to do copy assignment from d1 to d.
Okay, so I think that's -- I think you get the picture of what's going on.
It'll definitely make sense now when we step through.
It so let's start the debugger and step through this example.
So we're right here on line 69.
We're just about to construct that d object.
We expect this constructor right here on line 43 to execute.
That's the overloaded constructor that expects an integer.
So I'll step in.
And that's exactly where I'm at.
Now I need to construct the base part of me.
So I'm going to invoke my base classes, overloaded
constructor and pass in x to it.
So you can see that that's what's happened now.
I'm now in my base classes constructor, which is going
to take care of initializing that base part of me.
So we'll do that.
We'll display base constructor, and you can see now that I'm on line 45.
So I'm back in my derived constructor.
It just initialized the base.
It initialized double val, and now it's just going to do a simple
display statement, and we're good.
So we've just created that d object.
And you can see the d object looks like 100 for my base value
and 200 for my doubled value.
Okay, so now let's look at the copy constructor, that's
what's being called here.
Remember, we're constructing -- let me scroll up just a little bit here.
We're constructing d1 based on d.
So what we expect to be called is our copy constructor
right here on line 47.
So let's do that, so I think I double clicked.
What really happened was that our derived copy constructor got called.
And then it immediately called our base copy constructor
and we passed in other.
Now remember, other was or is a derived object here.
But now I'm up here in my base class copy constructor, but look at other.
You can see what I passed in.
Now it's a 100, right.
But other originally had that doubled value as well.
So that got sliced out.
And the only piece that we're interested in is the
base part of that object.
So that's what's happening here.
So we'll continue executing this.
Now we just copy constructed our base.
And when we come back right here on line 48 and 49,
we've set double value.
And now our derived copy constructor is done.
So you can see that the derived copy constructor, and you'll see that
on the output right here,
you see that the derived copy constructor
invoked the base copy constructor.
And it passed in that derived object to it, which got sliced.
Okay, so last thing left to do is the assignment
operator, the copy assignment.
So now we're going to say d equals d1 or assign d1 to d, right.
So we're here.
We expect line 52 to execute right here, that's our copy
assignment for derived.
So I'll click this.
I'll try to click it once.
Good. So now that's we're on line 53 now.
We're simply going to display derived operator equals.
We're going to check for self-assignment.
And now what I want to do is I want to copy the right-hand side to the
left hand-side but only the base part.
So in this case, I want to call my bass classes overloaded operator
and pass in right-hand side.
So now take a look at right-hand side.
Right-hand side is a derived, right.
So it's got a base, and it's got its derived double value here.
But watch what happens when I call that method, right here you'll
see I'll transfer control to it.
And this is where I'm at right here on line 25.
Look at that object now, look at right-hand side.
Notice that now it just -- it's a base now.
So it just got sliced, right.
That double part is not part of a base.
So it's sliced out.
So now we just keep executing, we're going to execute the base operator
equal, which assigns the right-hand side's value to the left-hand
side's value or this in this case since it's a member function.
And we just assigned the base part.
We're now on line 57.
We're going to take care of the double value part of us, and we are done.
That's it.
Pretty straightforward.
Now obviously these values are going to be the same because we
just assigned them, and they both have a base part and a derived part.
Now they're going to go out of scope, so we need to unwind
and then destroy these objects.
So the destructor will be called in the reverse order
of the constructors, right.
So the first destructor that will be called is the one for d1 here.
And we'll execute that.
And you can see that the derived destructor executes
followed by the base destructor right up here on line 32.
There we go, and then the same thing will happen with the next object.
And our program is done.
Okay, so this gives you some insight into the way that
the derived constructors and copy constructors work.
You can see it really makes sense.
The only thing that might be a little confusing at the beginning
is that, for example, our derived copy constructor and operator equal
expect a derived, and the base class versions expect a base, same here.
But that's where the slicing comes in.
We can certainly pass a derived into here because the derived is a base.
Okay, so that covers it.
If there are any questions, please let me know.