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; } |
![]() |
![]() |
![]() |
I was watching some talks from CppCon, and someone talked about, IIRC, copy-and-swap idiom. Apparently, this handles all edge cases safely. I think mentioning this would be great!
Great suggestion. I've flagged this for an update.
Nice! Thank you!
Also, on that note, I've come across 'The Rule of 3/3.5/4/5' in the talk. I'll drop the link here if my search is successful (gotta watch it again, now that I'm more or less knowledgeable of class semantics!). Mentioning those after this chapter on classes would be great! A lesson or pointing out to the reader that it's a good idea to have them would be great too!
I think you're talking about Arthur O'Dwyer's talk “Back to Basics: RAII and the Rule of Zero” ( https://youtu.be/7Qgd9B1KuMQ ).
I'm fairly certain the Rule of X is already in the lessons, but not named. If you come across it, please write a comment so it can be added for future reference.
Yes! That's the video! I love how he says RAII should've been 'Resource Freeing Is Deletion', but there's an acronym already for that . Thank you!
Well the topics have been covered (a constructor, destructor, copy constructor, etc) over the past few lessons, but yes, not named. I'm not sure if it can be specifically added under _one_ lesson though.
I understand this lesson and essentially what happens. My question is where would you want to do self-assignment. Like why would you want to make x = x becuase as said its a waste of processor speed? So having the safeguards will protect against that but unless it was unintentional why would you want to do self-assignment. I understand the safeguards will help prevent self-assignment but in what other cases would you purposefully do self-assignment.
Thanks!
You'd never self-assign like this
But self assignments can happen as part of something else, eg.
arr[0] will be assigned to itself.
I see. That makes sense. I was like why would you ever be like x=x? Anyways thank you for that. I will try to keep in mind the safeguards we can do.
Also, will we ever learn about things like std::fill? Or other things like that. I am just wondering becuase that would be nice. I understand what happens in that fill statement but anyways thanks!
This tutorial focuses more on core language mechanics than components provided via the standard library -- there's so much depth there it'd take a tutorial many times this size to cover it all in detail!
Once you finished the tutorials and have some spare time, browse through the standard libraries on cppreference
https://en.cppreference.com/w/cpp
Ok, thanks!
And a little typo in the "Issues due to self-assignment" section, second-to-last paragraph, second sentence: "str._m_data", where I believe "_" after "str" is redundant.
Fixed. Thanks!
Hey
I still don’t get one thing about copy initialization of classes. In what cases copy constructor and overloaded “=“ operator are called? When should I explicitly overload my own assignment operator?
You wrote:
“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.”
But I don’t understand what “has to be created” means here. I’d really appreciate if you could clear that out for me.
`operator=` is called when you do
where `a` and `b` are objects that already exist, ie. it's not an initialization.
If you're copying an object otherwise, ie. by passing to/returning from a function or initializing an object with another object, you can assume that the copy constructor is used. This isn't actually true, you'll learn about why in the lessons about move semantics.
> When should I explicitly overload my own assignment operator?
Whenever your class has members that cannot be shallow copied (Next lesson).
Thank you so much, finally I’ve understood that thing.
HELLO,
why should we delete the m_data, can we directly overwrite it?
If you allocate memory, you need to free it at some point. Otherwise you're leaking memory (ie. your program uses more and more memory, until you run out of memory).
If you override `m_data` without deleting it first, there's no way for you to delete the old memory.
Thank you so much,
Wouldn't it be safer to call the destructor instead of manually handling what is effectively the same destructor code again?
If you're sure that the destruct does what you need, you _can_ call the destructor instead. Calling constructors manually can cause problems. Maybe it's doing more cleanup than you need (eg. you could re-use the memory of the old string if the new string isn't longer). Maybe you want to debug your object's destruction to see if you're leaking something. A manual destructor call would be very confusion in such a situation.
Alternatively, you could add a (private) `free` function that deletes the old memory. Then call that from `operator=` and from the destructor.
Perfect reasoning! Thank you!
Hi Alex! You linked to this https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md in an updated tutorial, so since I am finished with your tutorials, I started reading them. Now, I don't quite understand the logic here, but they say not to check for self-assignment: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c62-make-copy-assignment-safe-for-self-assignment Another thing for your todo list when updating?
Hi Louis!
What they're saying is that a self-assignment occurs so rarely, that we're wasting a lot of processing power to perform a check that will almost never be true.
If that check is removed, we don't have that overhead anymore. The only time we waste processing power now is when a self-assignment occurs (But that's rare, so we can neglect it).
Allowing this self-assignment is only possible, because the members in the example were an @std::string and an int, both of which can handle self-assignments.
If one of the members couldn't handle self-assignments, we'd need a check. I think their rule would be better formulated as "Only write a self-assignment check if one of the members cannot handle self-assignments".
But then I don't understand the "Enforcement" part. Why would they consider it a "simple" check which can enforce the rule to say that [copy] assignment operators shouldn't contain "if (this == &a) return *this;"?
"(Simple) Assignment operators should not contain the pattern if (this == &a) return *this;"
That "simple" is the same as my "all members can handle self-assignment".
If your @operator= is not simple (ie. it has members that can't handle self-assignment), you can (and should) add a self-assignment check.
I understood the (Simple) as the simplicity of implementing a tool which can check the rule. I.e. "It is trivial to code a tool checking the following rule." I'll look at the introduction again to see if I can find an explanation of the (Simple) (Complex) etc. things in the enforcement sections.
Oh yeah, you're right. I understood it as an optional part of the sentence, as in
"Simple Assignment operators should [...]"
But looking at the other enforcements, (Simple) seems to be a tag. Let me know if you find anything further.
I found this issue: https://github.com/isocpp/CppCoreGuidelines/issues/838
This page referenced on it gives me a little better idea of what's going on: https://en.cppreference.com/w/cpp/language/operators#Assignment_operator
but i am still very fuzzy on what the guideline is trying to say. I can't really understand why you can skip the check.
do you check your cars light every time you get into your car? no, because if a light is broken, it's not that bad anyway.
The downsides of checking the lights every time you get into to car (it takes time) outweigh the downsides of having a broken light for 1 trip.
Same for the operator.
Running the comparison a million times is worse than forwarding the assignment to every child a couple of times.
Thanks! I think I have finally arrived! Follow-up question: if modern CPUs have branch predictors, why would you then not get the "best of both worlds" by including the check?
Although I think the majority of CPUs have branch predictors, I wouldn't rely on it. Optimization is good, as long as it doesn't make your code more complicated (IIRC that's also somewhere in that guide). Omitting the check makes the code easier, if anything.
I don't think I agree with the recommendation to blanket omit the self-assignment check in classes that would benefit from it.
The self-assignment check is just a pointer comparison, so it should be very fast. IMO, it's better to code defensively and then optimize precisely when and where needed (e.g. if you're calling this operator= a billion times).
I will update the lesson to note that the self-assignment check can be omitted if the members can handle self-assignment.
Yeah, the more I think about it, the more I think that rule is a mistake. Can one of you open an issue about it. I would, but I don't know enough to make useful suggestions.
So the default assignment operator does not so the self-assignment check, correct?
Because of that do you pretty much always write an assignment overload function?
> So the default assignment operator does not so the self-assignment check, correct?
Correct
> do you pretty much always write an assignment overload function?
No. You just don't assign an object to itself. If you do so on accident, you're wasting performance, but there won't be any problems.
Hello all,
I have been slowly but surely working through these chapters learning C++. I have a lingering confusion over distinguishing a pointer vs a reference to a variable.
Here is an example:
So this function to overload operator=, does it return an address to a fraction, or a reference to a fraction?
by returning *this at the end of the function, is that a give away that means it's an address? Also, I thought "*this" was a de-reference and would result in the actual data being pointed to. So does it return an address, or the actual data pointed to?
Lastly, in the "self assignment guard" if statement (this==&fraction), so I always read "&" in the parameter as "a reference to" the variable. But that statement is comparing it to "this" which is a pointer variable. So is it a pointer? Does including the "&" in the body of the function mean the data being pointed to?
Please clarify these issues. It really would help me a lot. Thanks.
Dean.
Whenever the function returns *this, it returns the actual value by dereferencing the this pointer to give the value (And in the operator= example, it returns a reference to the data, as the return type is Fraction&, and *this is an l-value).
When you instantiate a new pointer, you use the * in order to tell that it's a pointer storing address, when you use that in somewhere else, it is usually a dereference operator giving you the value inside the pointer.
You can distinguish the address-of operator and the reference in the same way:
When you instantiate a new reference, you use the & to tell that you are creating a reference.
When you use that in another place, it is usually the address-of operator, so when you check if this == &fraction, you check if the address stored in this is equivalent to the address-of fraction, and if that's true, then it will return *this, the value in the address the this pointer is storing (it will be true if you assign something to itself, as the this pointer is a pointer to the object storing the overload, and if the object storing the overload is assigned to itself, it doesn't instantiate new variables, and it makes sense that the address will be the same).
I hope it helps to clarify the answer to your question, it took me some time to understand how to distinguish those things, but after you understand how the compiler differentiates the operators, the topic becomes relatively easy.
As assign operator share the same memory for assign object.
suppose if we have two objects and A1 and A2;
A1 = A2;
Now change in A2 data can it reflect in A1 data or vice versa?
It shouldn't. Assignment should only copy values, not link the two objects together in some way. I discuss this further in the next lesson, on shallow vs deep copying.
Hello,
When we use operator= ,C++ always generates a copy constructor, just to work ? I mean operator= works with a copy constructor ?
C++ always generates a copy constructor for classes (unless you define your own, or tell it not to), whether you use operator= or not. Operator= does not use the copy constructor, it uses the operator= function.
Oh, ok, thank you.
Hi Alex,
Thanks for replying to my other questions. For some reason, my browser has no reply button so that I can say thank you to you regarding your prompt and wise response.
For this lesson, I have the following question.
By checking if our implicit object is the same as the one being passed in as a parameter, we can have our assignment operator just return immediately without doing any other work.
Since the == operator is not overloaded, we are just checking that whether the fraction's address is equal to this object's address. Am I correct?
Correct, pointer operators cannot be overloaded.
Hello Alex, first of all I want to compliment with you about this C++ course which is fantastic and very clear. I have a question about the "=" operator overload:
take this snapshot of code
How can resolve this problem?
I need to instantiate dynamically an array of custom class object (and I saw that keyword new is also instantiating that dynamic object with the default constructor) with a custom costructor parameter for each object.
Example:
Hi Fortunato!
Your compiler tried to generate a default Hello::operator= looking like so
Since your compiler failed to generate the operator= it marked it as a deleted function, so you can't use it.
You need to either manually define the operator= in @Hello or make @value non-const to allow the default operator= to function.
Hello Nascardriver, thank you for your reply!
Even redefining "=" operator (or defining because the default one is automatically discarded for the reasons you said),I can't assign a value to a static value.
In my project, I changed every const member in a non-const just because I want
that this assignment would be possibile. But doing that it's like I'm changing the behaviour of member's class.
> I can't assign a value to a static value.
You mean const? That's what const is for, it disallows modifications of a variable.
In the self-assignment example, you state, "Later on, when we’re copying the data from str into our implicit object, we’re accessing dangling pointer str.m_data. That leaves us either copying garbage data or trying to access memory that our application no longer owns (crash)."
str.m_data IS m_data, which you've just reallocated, so it should not actually be a dangling pointer anymore. Yes, it's uninitialized, so you've lost all of the data you had stored in it (it's now garbage data), but it shouldn't cause any memory access issues.
Good call. I've updated the article accordingly.
what does
mean?
Hi Cumhur!
Any number except 0 evaluates to true. If you apply the operator! this gets inverted. So everything but 0 evaluates to false.
Try to use the later variant to make your code easier to understand.
Hi again Alex,
In 9.12, the following line of code was shown:
According to this section the copy constructor will be used here since a new Fraction object "six" is being created. But won't operator= be called instead since there's a "=" in the line? I'm confused how C++ knows to call the copy constructor even when operator= is called. Does it have something to do with the fact that the above line of code is, as stated in 9.12, "evaluated the same way as":
i.e. the compiler does some kind of conversion from an operator= call to a copy constructor call?
Although initialization and assignment both use =, the compiler is smart enough to know whether we're initializing a new variable or assigning a value to an existing one. It can therefore call the appropriate function.
Since Fraction six is being defined on this line, this is an initialization, not an assignment. Initializations use constructors, whereas assignment uses operator=. It's a little misleading, since you may assume that the = literally means call operator=, but in this context, = is just part of the syntax for initialization, not an actual operator call. I suppose it's better than having to use a different symbol for initialization and assignment!
That makes sense! Thanks :)
Hello Alex. Would it be also OK, if I put const in front of "Fraction& operator= (const Fraction &fraction);". Like so: "const Fraction& operator= (const Fraction &fraction);". Since "=" operator is evaluated from right to left, it means that it doesn't really matter if reference or const reference gets returned. Right ? No extra benefit there, I'm basically just curious 'bout it. I've tried it out in Eclipse with mingw and it looks like it works alright. But I need to be sure, that I understand it correctly. I'm sorry if I've missed out anything, that was already mentioned in previous chapters, and bringing this up again. Many thanks in advance. Jan
No, you shouldn't do this, because then a statement like a = b = c wouldn't work.
Hello Alex. How does then the code below work ? The result is tripple 9/5 fraction. Notice, that I'm using const in front of the assignment operator declaration and definition. Thanks for clarification.
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
const 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)
const 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 f1(5,3);
Fraction f2(7,2);
Fraction f3(9,5);
f1 = f2 = f3; // chained assignment
cout << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;
return 0;
}
Consider f1 = f2 = f3. This evaluates as f1 = (f2 = f3). f2 = f3 calls function Fraction::operator= with f2 as the implicit object, so when we return *this, we're actually returning f2 after f3 has been assigned to it. Then the remaining expression is f1 = (f2), which works as expected.
So this works because f2 gets f3's value, and then f1 gets f2's value.
Exactly my point. But notice, I've used const in front of assignment operator method and it works. Ergo, my primary statement: "Would it be also OK, if I put const in front of "Fraction& operator= (const Fraction &fraction);"" is correct, right ?
Aah, sorry, I missed the actual question you were asking. This will most likely be fine. But I think it's better to return a non-const reference, otherwise statements like this won't compile: (x = y) = z;
Also, the C++ standard library uses non-const reference return values here, and I assume they've mulled whether const or non-const is better.
Point taken. Thanks.
Hi, Alex,
Why I didn't get an error with the self-assignment code? It just prints "Alex". I am trying the example codes on a Mac with gcc/g++ v4.2.1.
Thanks.
Accessing deleted memory results in undefined behavior. In this case, it appears that your "undefined behavior" is still producing the correct output (at least temporarily). That can happen, and when it does, it can make the problem hard to debug.
I add some codes into the assignment operator overloading function for debugging. It seems that it produced the correct output because the allocated memory was just the deleted, which are not override by other operations. At first I print both the c-string m_data's addresses, and check they are same; then I do some extra operations to allocate the memory and released immediately. Now the output is not "Alex" anymore, it's the content the last time I modified.
hi alex!
why the parameter "data" is treated like an array, is this because the compiler assumes that if assigned a string using "" the char pointer becomes a pointer to a char array?
hence the parameter is actually :
const char *data[]=""
sorry i realised that this was a declaration for a C-style symbolic constant
but how do we know whether the compiler treats it as a C-style symbolic constant string or a normal C-style string since the memory allocation is different for the two yet the syntax is so similiar
data is just a pointer that holds an address. It can hold the address of either a C-style symbolic constant, or a normal C-style string. It doesn't care about how the memory it points at was allocated.
Most often, we expect const char* to be C-style strings, which are arrays of char. However, note that there's nothing stopping the user from passing the address of a single char into this function, which will cause it to not work correctly (unless the char is '\0').
It's actually the equivalent of const char data[]="". That might a better syntax to use here, as it more explicitly indicates that an array is expected.
MyString& operator= (const MyString &str);
Hi,
I don't understand why there is a "&" after MyString in the code above (overload assignment)?
Yours sincerely,
Dai Nguyen
The & makes the MyString parameter and return value a reference, to prevent making copies of the MyString when MyString is passed as a parameter or returned to the caller.
Please review the lesson on references, passing values by reference, and returning values by reference for more information.
Hi Alex,
In the following example,
The following line would call the copy constructor as employee object is being created. Isn't it ?
Yes, my mistake (since I didn't overload the copy constructor for these examples). I've updated the example to do an assignment rather than a copy construct.
Under "Issues due to self-assignment", in the code for your overloaded operator<< function, you wrote:
"std::ostream& operator<<(std::ostream& out, const MyString &s)
{
std::cout << s.m_data;
return out;
}"
Did you mean to output to "out" instead of "std::cout"?
If so, out of curiosity, could you explain what would happen inside of this function call the way it is written now? I'm having trouble conceptualizing the flow of data here, and why it would matter whether we use "out" or "std::cout".
Yes, I meant out.
If operator<< was called with std::cout as the std::ostream parameter, then there would be no difference. But as you'll learn later, there are other std::ostream objects that could be used here, and if one of those had been used, s.m_data would have printed to the console rather than wherever that other object was pointing.
Hi,
In
shouldn't you use delete[] operator instead? Thanks
Yes. It's such an easy mistake to make. I've updated the example.
I think this operator overloading chapter needs one more article "overloading new and delete operator". It would be great, if we throw some light on it.
Thanks for the thought! Overloading new and delete is more of an advanced topic. Would be fun to explore that one some day, but most people don't write their own allocators (and the ones that do probably aren't going to come here to learn how to do so). :)
Hi Alex,
Why does the code below work without the overloaded operator=?
how is that differ from
main.cpp
Hand.h
Hand.cpp
This is an initialization, so operator= wouldn't be called. Here's what happens:
1) peter + wendy is evaluated first.
2) You've overloaded operator+ to cause peter + wendy to return an object of type Hand.
3) Object two is then constructed (using the copy constructor) with the object returned from operator+ as the argument.
Thank you!
I really like your comprehensive quizzes because they really help me review what I've learned. Since you don't have the comprehensive quiz for chapter 9 or 10, I have been working on extending the blackjack example from before.
I was wondering if you wouldn't mind taking a look at my code and comment on it especially errors in logic? (Once I'm done with it that is)
It is more than a few hundred lines long and several files so I don't think it would be appropriate to post it on here. What would be an appropriate way to get my program to you?
Either way I very much appreciate all of this!
Drop me a line via the comment form in the About / Contact section.
Hey Alex,
I'm done with the blackjack example. I dropped you a line via the comment form a while back but haven't heard from you. Was wondering if you still would want to take a look?
Hey Peter,
I've been slammed in real life, and have barely had time to respond to the comments here. I'll try to look at your code soon but I'm not sure when I'll be able to get to it. Sorry. :(
No problem!
I completely understand. When you do have sometime and willing, can you send me an email and i'll get my code to you?
Thank you very much!
Hi Alex,
Can you explain further why in the code:
Cents& operator= (const Cents &cSource);
We require operator= to be passed by reference?
I am assuming it's similar to the explanation of " .. Second, the parameter MUST be passed by reference, and not by value. Can you figure out why?" for the copy constructor part but it would be helpful if you could go into the same detail with this case as well so I can understand :)
Thanks!!
It's not required to be passed by reference. It's done for efficiency reasons, to avoid making an unnecessary copy.
Can you explain how self-assignments can show up in realistic code? Could they be auto-generated somehow?
I feel maybe an assert should be appropriate if it is a real issue to get rid of them :-/
Self-assignments aren't common (particularly in string classes) but it is possible to inadvertently get them in other cases (I can't think of any good examples).
There's really no reason for a self-assignment to fail. The generally-intended end-result of the following statement:
is that "a" should equal whatever it did before this statement (unless you overloaded operator= to do something weird, like reference counting).
So we just special-case the self-assignment case and go on with life. :)
Hi Alex,
This tutorial was really helpful, I've one doubt, is it necessary to have assignment operator ,if copy constructor is already present, if we are sure that we are not using any assignment ( only instantiating the new object from existing one, in this case only copy constructor is enough rt ? ), do we still need to overload assignment operator ?
No, it's not required to have an assignment operator (if you don't create one, the compiler will create one for you and it will do a shallow copy). However, this is generally a bad idea because when you look at your code in 3 months you're likely to forget you didn't overload the assignment operator and then when you try and use it, you won't get the results you expect.
Hello again Alex -
I am having trouble grasping when and where to use *this. I will go back and re-read that chapter, but it occurs to me that it might help me (and others) if you had a symbolic diagram of the stack so I could clearly see what's going on when *this is returned by a function.
Thanks - this website is a great intro to C++.
"return *this" simply returns the object that the member function is working with to the caller.
Thus, if you called:
foo.someMemberFunction();
and someMemberFunction() returned *this, someMemberFunction() would return foo.
The hardest thing for most people seems to be just understanding what the this pointer is doing in the first place. The actual return mechanics are the same as with any other return value.
This confused me for a bit, too, so I'll offer my understanding that it might help someone else.
Returning *this will dereference the "this" pointer, and instead return the actual object itself. What I got tripped up on is why it wasn't thus creating a copy of this and returning that. The answer is because this function returns a Cents& (reference). As a result, the object is not copied and instead the capturing value gets the proper object.
Why cant we overload assignment operator using a friend function.
The operators (), [], -> and = can be overloaded only as class members; all other overloaded operators can (also) be implemented as friends
If you could define operator= as a non-member, then that also means you could define the overridden operator= local to a file. That could lead to inconsistencies, where operator= used the default behavior everywhere except in that one file.
Forcing operator= to be a member avoids this situation.