In this video, we'll learn how to overload the copy assignment operator.
The assignment operator is used by c++ when it assigns one object to another object.
Don't confuse assignment with initialization.
Initialization is done by constructors when we create new objects.
The statement mystring s2 equals s1
is not an assignment statement, it's an initialization statement
because s2 hasn't been created yet.
C++ allows us to use the assignment operator in this context
but this assignment is really equivalent to mystring s2 from s1.
As shown in the comment, it will invoke the copy constructor.
Notice the statement s2 equals s1.
This is assignment since s2 has already been created and initialized
previous to the statement.
So an assignment occurs when an object has already been initialized
and you want to assign another object to it.
C++ provides a default way of assigning one object to another.
And if you don't provide your own overloaded assignment operator,
then c++ will provide a compiler generated one for you.
This is very similar to what it did with the default copy constructor.
The behavior for the default assignment is member-wise assignment,
which means shallow copying.
If your class doesn't have raw pointers,
then the default assignment operator will probably be just fine.
However, in mystream class, we have a raw pointer,
so we'll overload the assignment operator
so that it deep copies the pointed two data on the heap.
So how exactly do we overload the copy assignment operator?
First, the assignment operator must be overloaded as a member function,
so we can provide a method prototype
that has the general pattern shown in the top declaration.
We then simply replace type with the name of our class.
Notice that the name of the method is operator equal.
That's how we declare methods that overload operators.
The keyword operator followed by the operator that we wish to overload.
So for the mystring class,
the overloaded assignment operator method returns a reference to a mystring object.
This is important since we want to avoid extra copies
if we return by value.
And we also want to allow chain assignments, such as p1 equals p2 equals p3.
We'll define this method in the next slide,
but once it's defined, we can write code like s2 equals s1,
which assigns object s1 to object s2.
That's very readable in writable code,
and it feels exactly like if we were assigning one of the c++ built-in types,
but now it's working with our own user-defined classes.
That's pretty easy and pretty neat.
The compiler will actually convert the s2 equals s1 assignment statement
to s2.operator equal and pass in s1 behind the scenes.
Let's see how we would implement the copy assignment for the mystring class.
Here's the code for the overloaded copy assignment operator.
It looks complicated but it really isn't.
We'll break this down into the basic steps over the next few slides.
Remember, the object on the left-hand side of an assignment statement
is referred to by the this pointer.
And the object on the right-hand side is being passed into the method.
I've named the right-hand side object RHS for right-hand side to be clear.
Since we're assigning the right-hand side object to the left-hand side object,
we must make a deep copy of the right-hand side object's attributes
and copy them over to the left-hand side object.
So what does this mean semantically?
Well, the left-hand side object or this object will be overwritten.
So we need to de-allocate anything it refers to on the heap.
Then we have to allocate space
in the left-hand side object for the right-hand side object's data,
and then we finally copy the data over to the left side from the right side.
Let's go over this code one statement at a time
since I know it can be hard to follow when you're first learning this.
The first thing we want to do is check to see if we're doing self-assignment.
For example, p1 equals p1 or assign p1 to itself.
We can easily check for that by checking the address of the left-hand side object,
which is in the pointer this
and comparing it to the address of the right hand object.
If they're the same, then we really don't want to do anything and we just return
de-reference this, which is the left-side object.
If we don't have self-assignment, then we need to de-allocate storage
for this string since it's pointing to a string on the heap.
We need to de-allocate the storage. Otherwise when we copy the new data over,
we'll orphan this memory and end up with a memory leak.
Now the left side object is ready to be assigned from the data from the right side object,
but we haven't yet allocated storage on the heap for the deep copy.
So we need to allocate enough storage on the heap,
that's the size of the string in the right object plus 1
since we need the space for the string terminator.
That's it. Now we can do the copy.
We can perform the copy by copying over one character at a time
until we see the null character.
But since we're using a c-style string,
we can simply call the string library method stood string copy,
which copies the string from the right-hand side object to the left-hand side object.
That's it. We're done. All we have to do now is return the left side object so we can support
chain assignment.
This may seem like a lot at first,
but it's a pattern or idiom that we always use in c++ when we're doing deep
copy assignment.
In this case, we only have a single raw pointer.
If your class has multiple raw pointers, then don't forget to deep copy each of them.
Let's go over to the IDE and step through copy assignment using the debugger
so you can see exactly how this works in live code.
Okay. So I'm back in the IDE. And in this case, I'm in the section 14 workspace,
and I'm in the mystring_copy_assignment project.
What we want to do now is
we've got our mystream class and it's really basic. Right now it's only got the constructors,
the copy constructor, the couple of getters and the display,
just as we've seen before. And what we want to do is we want to add
the copy assignment operator. We want to overload that and add it to the class.
But before we do that, let's talk about the semantics of exactly what we're trying
to achieve. So I've written four lines of code right here
in my main. I've actually got a lot more in the main, which i will uncomment out later.
So let's talk about these four lines of code and talk about it in the context of assignment.
You can see what's going on here. I'm instantiating my object a.
It's a mystring object, and I'm initializing it to hello.
So when this line executes, I will have something like this. I'll have my a object.
It will have an str attribute, which is a pointer.
Hello will be allocated on the heap.
Remember, there's a null character there at the end. So this is really six characters big,
and that str attribute will be pointing to it.
So that's the condition that I'll be in after the construction of the a object.
Then when we create the b object, will be in a situation like this
where this will be b, this will be str again.
In this case, I'm pointing to an empty stream because that's
how we defined our no args constructor,
so will be something like that.
Remember, the empty string consists of a single character with the null character in it.
Then we're going to execute this code right here, b equals a or assign a to b.
In this case, this is assignment
because b already exists, b has been instantiated.
So what we want to happen is a
couple of things. First, we want to be sure that we're not assigning to ourselves,
b equals b or a equals a, that's easy to do.
Then what we'll do is we really want b to contain that hello, right.
But we don't want this situation like we had with a shallow copy.
We want to create a copy of that hello string
on the heap. So in order to do that, there's a couple of things I need to do.
First of all, I need to de-allocate this right,
and this could be a lot bigger than just an empty string. This could be a very large string.
So i want to de-allocate that.
Then I want to allocate space for 6 characters, right, because I'm
copying hello in there and hello as h-e-l-l-o and the null terminator.
I want to be in this situation, I want to copy
hello over.
And at this point,
what we've got is this kind of situation. All this is gone now.
That's been de-allocated so we don't leak memory there.
And this is where we want to be with b.
Okay. So that's where we're headed. You can see the steps that we need to do visually here.
We're going to do them in code next.
Now this example here is also an assignment because we're assigning something to b
and b already exists. In this case, it's a little bit different because
we don't have an operator equal that expects a character pointer,
right, which is what this is right here, c-style string.
So what we need to do is we need to construct a temporary object
using the overloaded constructor just like what we did over here,
and then assign that temporary object to b and then that will eventually be destroyed,
the temporary object will. Okay. So that's it. That's where we're going.
Let me clear this, and we'll start writing the code now.
And then once we're done, we'll walk through this example.
So let's start with our header file.
What we need to do is we need to write the prototype here,
and I'll leave a little bit of extra space, so it's really clear for the
overloaded assignment operator.
So we know it returns a mystring object by reference.
We know that the name is operator equal,
that's just the way we name operator methods in c++.
That just tells the compiler they're overloading the
equal or assignment operator.
What does it expect? Well, the right-hand side. Remember, the left-hand side is this. I
It's a pointer the current object. The right-hand side is simply going to be a constant
mystring reference.
And I'll name it right-hand side. We don't have to provide a name here in the prototype.
And I'll just say copy assignment,
that's our prototype. Again, be really sure that you understand this.
We're overloading the equal operator or the assignment operator.
The left-hand side is the current object.
The right-hand side is what we're assigning.
And we want this guy const because we really don't want to mess with it.
It's not our object, so I don't want to mess with it at all.
Also remember, this is not construction. This is assignment.
That left-hand side already exists.
Okay. So now let's write the code for that copy assignment.
Now I've made a little room here so that I can
put in the code and we've also got some break points. So I've already gone ahead and put one in.
So what we'll do is we're now going to implement that. So mystring,
right, we're returning a reference and this is
operator
equal.
What does it expect, just like the prototype costs to mystring
reference right-hand side.
Okay. Now the first thing I want to do is I just want to do a little output
statement so that we know when this is being called.
So I'll just say std cout,
and we'll just say something like copy assignment.
And we'll just std endline.
Perfect.
So what do we need to do first? Well,
first thing we need to do is make sure that we're not assigning to ourselves. That's simple enough.
If this -- now remember,
this is a pointer to the current object and a pointer holds an address.
So if this is the address of the right-hand side,
in other words, really the same object,
then all I want to do is just return this,
okay. And we need to de-reference this in this case.
Remember, if we just say return this, this won't compile because then that means that this guy would be
expecting a pointer back,
but we're not. We're doing a reference back, not a pointer.
Okay. So we're gonna pass up the reference back.
That's it.
Now we start doing the real work. First thing we need to do is,
remember, that this object is the one that's being copied into.
So it may be pointing to something, right,
that str attribute may be pointing to a string.
Actually, it is pointing to a string even if it's just an empty string.
So what we need to do is just delete
and make sure you use the brackets here because it's an array of characters we're deleting
str,
okay. If this makes it more clear,
then feel free to use the this keyword right there,
it's all the same.
Then we're going to say str equals
new character. We need to allocate that array of characters on the heap.
And how many characters do we need to allocate? Well,
we're copying the right-hand side object data into it, right.
So the right-hand side object has an str attribute.
So however big that string is
plus 1 because we need space for the string terminator.
So how big is this going to be? Well, it's going to be --
take the string length of not str
right but right-hand side str.
And since we're a member method we have access to that and we want to add 1.
Perfect. So now we've allocated the space that we need on the heap,
the only thing left really is to copy that string over from the right-hand side and then just return,
so let's do that. So we'll do stud string copy
to str again,
you could say this str if that's clear.
From RHS, right-hand, side string so that's.
The string copy the string copy function simply copies one character at
a time until it hits the terminating character.
It copies that as well, then it's done.
And the last thing to do is just return
the object that we just copied into, the left-hand side.
That's it. Let's go and run this.
But before we do that, let's actually debug it. So I'm going to go back to my main,
and let's start the debugger. So this is where we're at.
Let's step through this so we can really understand what's going on here.
At this point, I'm on line 10.
I'm constructing or instantiating that a object, right.
And I'm providing some initialization information, this string.
So in this case, I'm going to call the overloaded constructor.
So let's do that. Let's step through.
There is the call to the overloaded constructor that expects
a character pointer just like we expect, it's not null. So we're gonna skip the if part,
and we're right here. So how many characters are we allocating for this guy?
Well, whatever the string that was being passed in by plus 1 for the null terminator.
And then we're just copying it over. So let's step through this.
And when we get back here, you can see that a right here,
that str attribute is pointing to the string hello,
exactly like we expected.
The b object, same idea except we're calling a no args
constructor since we're not providing any information.
And when we get back, we expect b's string attribute to be an empty string.
be an empty string. So let's do that.
There you can see the no args constructor. It's simply creating one character
and putting the null terminator in there.
So at this point, you can see that
a is hello and b is that empty string right here.
Now is the assignment. I'm assigning a to b.
So let's step through this. We expect operator equal to be called,
and that's exactly what happens right here. We're on line 38 of the
copy assignment overloaded method.
We display copy assignment.
We're going to check for self-assignment.
Here we don't have self-assignment, so that will be skipped.
Now we're going to delete the left-hand side object's
string on the heap. So we're deleting that.
And at this point, we're going to copy.
And you can see that the right-hand side is hello
and the left-hand side right now is just pointing to garbage, right,
because we've deleted that, so it's invalid memory.
We're really pointing anywhere right now.
So what we're going to do is simply replace that with
an area that I'm creating on the heap that's going to store the string that,
I'm just about to copy. So let's do that now,
and then we're going to copy that string over.
So at this point, I'll refresh here.
And you can see that right-hand side was still hello, and now this
is also hello. And there you go.
And you can see from the addresses, they are indeed two unique areas in memory.
I'm returning the object by reference,
and that's it. I'll refresh this side again. And now we've got a and b,
and they should be the same: a is hello, b is hello, and there, again,
two different areas in memory.
This example here what's going to happen is on line 14 is
we're going to call the overloaded operator, again, to create that object.
And then once that object is created, we'll assign it over.
So that temporary object is going to be assigned over. So let's just walk through that really quickly here.
And you can see right here I'm calling the overloaded constructor,
which is going to create a temporary object.
And now that temporary object
will be assigned. So you can see the assignment operator being called now.
So I'll walk through that again. We delete what we're at.
We move it over.
And we get rid of the temporary object. That's the destructor being called right there.
That's it so we're done here.
You can see a is hello, and b is now this is a test.
And as we unwind, we're going to
call the destructor for b and then the destructor for a,
and our program is done.
Okay. So that gives you an idea of the assignment operator
the copy assignment operator. It's pretty straightforward. And what I've also done is
I've created some other test cases here that you can walk through and learn from.
And what I'll do is I'll just uncomment them out, and then I'll run the program really quickly.
So what I've got is I've just got some examples of
creating more objects and assigning objects to vectors and so forth
and you can see that when we do things like s equals changed,
we're going to create a copy assignment. And that's exactly what we want.
We don't really want to create that guy using the conversion constructor
and then use a copy constructor to copy stuff over.
We're just doing assignment, and we don't want to do
anything related to shallow assignment, right.
We want to do deep assignment. Otherwise, we're going to end up with all kinds of problems.
So I'll run this, and there's the output. You can see that we've got
copy assignment happening.
We've got Larry a bunch of times. And like I said, you can run through this
and walk through it yourselves. It's really important that you
step through a program like this. You can really, really understand what's going on.
But you can see that copy assignment is called all over the place here because that's exactly what we want.
Okay. So now in the next video what we'll do is
we'll do move assignment, which is pretty powerful.
And it'll take care of assigning temporary
values much more efficiently than copy assignment.