The assignment operator (operator=) is used to copy values from one object to another already existing object.
Assignment vs Copy constructor
The purpose of the copy constructor and the assignment operator are almost equivalent -- both copy one object to another. However, the copy constructor initializes new objects, whereas the assignment operator replaces the contents of existing objects.
The difference between the copy constructor and the assignment operator causes a lot of confusion for new programmers, but it’s really not all that difficult. Summarizing:
- If a new object has to be created before the copying can occur, the copy constructor is used (note: this includes passing or returning objects by value).
- If a new object does not have to be created before the copying can occur, the assignment operator is used.
Overloading the assignment operator
Overloading the assignment operator (operator=) is fairly straightforward, with one specific caveat that we’ll get to. The assignment operator must be overloaded as a member function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#include <cassert> #include <iostream> class Fraction { private: int m_numerator; int m_denominator; public: // Default constructor Fraction(int numerator=0, int denominator=1) : m_numerator(numerator), m_denominator(denominator) { assert(denominator != 0); } // Copy constructor Fraction(const Fraction ©) : m_numerator(copy.m_numerator), m_denominator(copy.m_denominator) { // no need to check for a denominator of 0 here since copy must already be a valid Fraction std::cout << "Copy constructor called\n"; // just to prove it works } // Overloaded assignment Fraction& operator= (const Fraction &fraction); friend std::ostream& operator<<(std::ostream& out, const Fraction &f1); }; std::ostream& operator<<(std::ostream& out, const Fraction &f1) { out << f1.m_numerator << "/" << f1.m_denominator; return out; } // A simplistic implementation of operator= (see better implementation below) Fraction& Fraction::operator= (const Fraction &fraction) { // do the copy m_numerator = fraction.m_numerator; m_denominator = fraction.m_denominator; // return the existing object so we can chain this operator return *this; } int main() { Fraction fiveThirds(5, 3); Fraction f; f = fiveThirds; // calls overloaded assignment std::cout << f; return 0; } |
This prints:
5/3
This should all be pretty straightforward by now. Our overloaded operator= returns *this, so that we can chain multiple assignments together:
1 2 3 4 5 6 7 8 9 10 |
int main() { Fraction f1(5,3); Fraction f2(7,2); Fraction f3(9,5); f1 = f2 = f3; // chained assignment return 0; } |
Issues due to self-assignment
Here’s where things start to get a little more interesting. C++ allows self-assignment:
1 2 3 4 5 6 7 |
int main() { Fraction f1(5,3); f1 = f1; // self assignment return 0; } |
This will call f1.operator=(f1), and under the simplistic implementation above, all of the members will be assigned to themselves. In this particular example, the self-assignment causes each member to be assigned to itself, which has no overall impact, other than wasting time. In most cases, a self-assignment doesn’t need to do anything at all!
However, in cases where an assignment operator needs to dynamically assign memory, self-assignment can actually be dangerous:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#include <iostream> class MyString { private: char *m_data; int m_length; public: MyString(const char *data="", int length=0) : m_length(length) { if (!length) m_data = nullptr; else m_data = new char[length]; for (int i=0; i < length; ++i) m_data[i] = data[i]; } // Overloaded assignment MyString& operator= (const MyString &str); friend std::ostream& operator<<(std::ostream& out, const MyString &s); }; std::ostream& operator<<(std::ostream& out, const MyString &s) { out << s.m_data; return out; } // A simplistic implementation of operator= (do not use) MyString& MyString::operator= (const MyString &str) { // if data exists in the current string, delete it if (m_data) delete[] m_data; m_length = str.m_length; // copy the data from str to the implicit object m_data = new char[str.m_length]; for (int i=0; i < str.m_length; ++i) m_data[i] = str.m_data[i]; // return the existing object so we can chain this operator return *this; } int main() { MyString alex("Alex", 5); // Meet Alex MyString employee; employee = alex; // Alex is our newest employee std::cout << employee; // Say your name, employee return 0; } |
First, run the program as it is. You’ll see that the program prints “Alex” as it should.
Now run the following program:
1 2 3 4 5 6 7 8 |
int main() { MyString alex("Alex", 5); // Meet Alex alex = alex; // Alex is himself std::cout << alex; // Say your name, Alex return 0; } |
You’ll probably get garbage output. What happened?
Consider what happens in the overloaded operator= when the implicit object AND the passed in parameter (str) are both variable alex. In this case, m_data is the same as str.m_data. The first thing that happens is that the function checks to see if the implicit object already has a string. If so, it needs to delete it, so we don’t end up with a memory leak. In this case, m_data is allocated, so the function deletes m_data. But because str is the same as *this, the string that we wanted to copy has been deleted and m_data (and str.m_data) are dangling.
Later on, we allocate new memory to m_data (and str.m_data). So when we subsequently copy the data from str.m_data into m_data, we’re copying garbage, because str.m_data was never initialized.
Detecting and handling self-assignment
Fortunately, we can detect when self-assignment occurs. Here’s an updated implementation of our overloaded operator= for the MyString class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
MyString& MyString::operator= (const MyString& str) { // self-assignment check if (this == &str) return *this; // if data exists in the current string, delete it if (m_data) delete[] m_data; m_length = str.m_length; // copy the data from str to the implicit object m_data = new char[str.m_length]; for (int i = 0; i < str.m_length; ++i) m_data[i] = str.m_data[i]; // return the existing object so we can chain this operator return *this; } |
By checking if the address of our implicit object is the same as the address of the object being passed in as a parameter, we can have our assignment operator just return immediately without doing any other work.
Because this is just a pointer comparison, it should be fast, and does not require operator== to be overloaded.
When not to handle self-assignment
First, there is no need to check for self-assignment in a copy-constructor. This is because the copy constructor is only called when new objects are being constructed, and there is no way to assign a newly created object to itself in a way that calls to copy constructor.
Second, the self-assignment check may be omitted in classes that can naturally handle self-assignment. Consider this Fraction class assignment operator that has a self-assignment guard:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// A better implementation of operator= Fraction& Fraction::operator= (const Fraction &fraction) { // self-assignment guard if (this == &fraction) return *this; // do the copy m_numerator = fraction.m_numerator; // can handle self-assignment m_denominator = fraction.m_denominator; // can handle self-assignment // return the existing object so we can chain this operator return *this; } |
If the self-assignment guard did not exist, this function would still operate correctly during a self-assignment (because all of the operations done by the function can handle self-assignment properly).
Because self-assignment is a rare event, some prominent C++ gurus recommend omitting the self-assignment guard even in classes that would benefit from it. We do not recommend this, as we believe it’s a better practice to code defensively and then selectively optimize later.
Default assignment operator
Unlike other operators, the compiler will provide a default public assignment operator for your class if you do not provide one. This assignment operator does memberwise assignment (which is essentially the same as the memberwise initialization that default copy constructors do).
Just like other constructors and operators, you can prevent assignments from being made by making your assignment operator private or using the delete keyword:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <cassert> #include <iostream> class Fraction { private: int m_numerator; int m_denominator; public: // Default constructor Fraction(int numerator=0, int denominator=1) : m_numerator(numerator), m_denominator(denominator) { assert(denominator != 0); } // Copy constructor Fraction(const Fraction ©) = delete; // Overloaded assignment Fraction& operator= (const Fraction &fraction) = delete; // no copies through assignment! friend std::ostream& operator<<(std::ostream& out, const Fraction &f1); }; std::ostream& operator<<(std::ostream& out, const Fraction &f1) { out << f1.m_numerator << "/" << f1.m_denominator; return out; } int main() { Fraction fiveThirds(5, 3); Fraction f; f = fiveThirds; // compile error, operator= has been deleted std::cout << f; return 0; } |
![]() |
![]() |
![]() |
Hi, under "Detecting and handling self-assignment" i think line 14 should be,
since we should be using the value assigned to m_length by str.m_length
The previous line is
m_length = str.m_length
so your code and the implemented code do the same thing.Is there some reason you think m_length should be preferred?
Yes, they are the same but actually using m_length will tell the reader that we are using the new assigned value since in the previous line we wrote "m_length = str.m_length". So i thought using m_length will make more sense in this case :)
I have a doubt in this program:
It's output on my machine:
Since the default assignment operator of a class does memberwise initialization, when you do alex2 = alex, it should do something analogous to alex2.m_data = alex.m_data. So m_data of both alex and alex2 should be pointing to the same const char* object. Why are their addresses different in the above code?
Never mind I recognized the mistake. I am printing the address of char* object which will be different for both objects. Now that I convert char* to void* and print they show the same address. But I also noticed that if I don't create alex2 object and then separately assign alex but do MyString alex2 = alex, it uses the default copy constructor. Any way to make the compiler do what's exactly written in the program?
Hi srt1104
The issue is that one cannot "have a doubt" "in" something.
You can have doubts "about" something. The doubt belongs to you personally, and therefore in will mean there is a copy of you doubt in the other object.
Perhaps, your failure to understand copy constructions in c++ is due to your failure to speak proper english
Hello, I was wondering why you don't cover the copy-and-swap idiom in this lesson ? It seems to be a widespread good practice, and helps reduce code redundancy by sharing logic between the copy constructor and copy assignment operator.
It seems like the examples you give here for overloading the assignment operator aren't really necessary, as it just re-implements the default assignment operator provided by the compiler (and, for the first few examples, in ways you recommend against!).
Could you give examples where it would be a good idea to overload the assignment operator?
I suppose one might want to do so if you want there to be side-effects to assignment (maybe you want to print some text whenever assignment occurs; I don't know). However, I can't really think of a circumstance where this is a really good idea.
Oh, I see. I think you discuss this in the next lesson.
Can the following be correct for the assignment operator? The memory (m_data) is deleted only if the m_length is not greater or equal than str.m_length.
You need to write the null-terminator if you keep the old memory. Otherwise you'd mix strings.
>> In this case, m_data is allocated, so the function deletes m_data. But str.m_data is pointing to the same address! This means that str.m_data is now a dangling pointer.
I think when m_data is deleted, str.m_data is deleted at the same time too as 'str' is just an alias for '*this'. Then why would we say 'str.m_data is now a dangling pointer'. This sentence somehow implies that str.m_data is different and separate from m_data. On the other hand, shouldn't we say, 'str.m_data is now a dangling pointer and so is m_data'?
I updated the lesson to be more clear about *this and str being the same object.
Does assignment operator use Memberwise initialization too?
And, the reason you used for-loop instead of 'm_data=data' in the code below is because we got error of converting const to non-const in char* data type?
It performs memberwise assignment.
You can't just copy `m_data`, you be performing a shallow copy (next lesson).
>>Our overloaded operator= returns *this, so that we can chain multiple assignments together:
I thought returning by reference & is the reason for chaining multiple assignments together. Why do we return as reference '&' for assignment operator? is that just because to prevent copying?because we didn't use for 'copy' constructor.
It's for chaining
>>If a new object has to be created before the copying can occur, the copy constructor is used (note: this includes passing or returning objects by value).If a new object does not have to be created before the copying can occur, the assignment operator is used.
Line 14 doesn't follow the rule above. Is that because of elision feature of compiler for copy constructor?
Defaul constructor called
5/3
Defaul constructor called
3/4
Assignment operator called
5/3
Defaul constructor called
7/5
Copy constructor called
5/3
Yes. No matter how often you call a constructor during the creating of a single object, only 1 object is constructed. All other calls are elided.
Was "// A simplistic implementation of operator= (do not use)" a copy-paste mistake in section "Detecting and handling self-assignment"?
EDIT: Nvm, looking at section 9.15 it seems the comment was correct. Could maybe say "// A simplistic implementation of operator= (do not use, we will continue improving this in Section 9.15)".
Thank you so much for the great article!
Please tell me why there is a note "(do not use)" in the following example? I think that everything is OK here.
In the book I am reading, it is written about overloaded assignment operator with its return type as constant reference :
(where Array is a user defined class implementing dynamic array)
It says:
" Regardless of whether this is a self-assignment, the member function (talking about the assignment operator overloading function) returns the current object (i.e., *this) as a constant reference; this enables cascaded Array assignments such as x = y = z, but prevents ones like (x = y) = z because z cannot be assigned to the const Array reference that’s returned by (x = y). "
-> Why is (x=y)=z any different than x=y=z?
->What is the difference between
and
The assignment operator is evaluated right-to-left, ie.
Here, it doesn't matter whether `(d = e)` is `const` or not, because it doesn't get modified. In
`(x = y) = z`, we're trying to modify a `const` reference, which doesn't work.
I can't tell which version is better.
Thanks! Cleared my doubt!
If you return by const reference then you can't chain other non-const operators or member functions.
However, it may be better practice to return const reference to encourage splitting up expressions into multiple easier-to-read statements. It is standard for assignment expressions to stand in their own statements.
I have a question in the following code.As you mentoned,"If a new object has to be created before the copying can occur, the copy constructor is used",so why is the copy constructor not used ? We have to create a new object named f before we copy the contents of the object named fiveThird into the object named f.
I think this behavior meets your description for the copy constructor.Does my concept be wrong?
alright. So I think i have an answer. I don't know if this will answer your question so Alex or Nascardriver feel free to corret me if i'm wrong.
The assignement operator copys and replases the content of the object with new values that are being assigned. In order for this to work though the object have to exist otherwise you can't replase anything since there is not an existing object to plase the copied content from the copied object in. So if you do something like this, Fraction f3=f1 the compiler will check if f3 already exists. The compiler will then determine that f3 have not been created before so assigning a value to it won't work. The compiler will instead create f3 and use the copy constructor.
You can test this to see what I mean.
Sorry if this is more rambling then a good answer but the best way to understand the diference is to test your code.
f exists as of the line
There the default constructor is called (`Faction(0,1)`) since a new Fraction object is to be constructed and since you didn't provide arguments for the existing constructor Fraction(int,int) the default arguments are used. You could verify this by modifying said constructor to print a message every time it is called and modifying the main function to print a message between every statement. The line
doesn't call a constructor since we are using existing objects ( f and fiveThirds).
You could think of the line
as of
ie. the call to a constructor
(which is provided by default but can be overridden) the result being named 'f'. For that to work 'f' need to not exists.
You could think of the lines
as of calling the default constructor in the first line and then calling
ie a member function
named "operator=". For that to work 'f' needs to already exist (since else there wouldn't exist a valid address for the "this" keyword to be replaced with).
While the both seem to be totally the same as it's seemingly just one statement spread to two lines they're not. The second one works with an already existing object and thus doesn't call a constructor. For example
Inside the assignment operator of MyString, do I really need to delete[] m_data? Reusing the allocated memory would lead to problems? If instead of deleting and allocating I do something like:
Would it be wrong?
If the new string is not longer than the old string, you can (and it's good to) re-use the memory.