Why should the assignment operator return a reference to the object?
Asked Answered
M

4

28

I'm doing some revision of my C++, and I'm dealing with operator overloading at the minute, specifically the "="(assignment) operator. I was looking online and came across multiple topics discussing it. In my own notes, I have all my examples taken down as something like

class Foo
{
    public:  
        int x;  
        int y;  
        void operator=(const Foo&);  
};  
void Foo::operator=(const Foo &rhs)
{
    x = rhs.x;  
    y = rhs.y;  
}

In all the references I found online, I noticed that the operator returns a reference to the source object. Why is the correct way to return a reference to the object as opposed to the nothing at all?

Medieval answered 30/1, 2012 at 23:6 Comment(3)
The correct way is whatever way implements the semantics you want; the idiomatic way is certainly to return T& (Foo& in your example).Inositol
@MooingDuck, I guess I phrased the question wrong. I was going on the assumption that my notes were wrong, but wanted to know why more than which was correct.Medieval
possible duplicate of assignment operator return a reference to *this in C++; also Returning *this with an assignment operatorHistrionic
S
26

The usual form returns a reference to the target object to allow assignment chaining. Otherwise, it wouldn't be possible to do:

Foo a, b, c;
// ...
a = b = c;

Still, keep in mind that getting right the assigment operator is tougher than it might seem.

Sophister answered 30/1, 2012 at 23:8 Comment(1)
Never knew about the Copy and Swap part. I've always just checked for self assignment, assigned values, and returned void, I guess there's more to this than I was expecting. Accepting your answer for pointing out the Copy&Swap Thanks for the response.Medieval
S
17

The return type doesn't matter when you're just performing a single assignment in a statement like this:

x = y;

It starts to matter when you do this:

if ((x = y)) {

... and really matters when you do this:

x = y = z;

That's why you return the current object: to allow chaining assignments with the correct associativity. It's a good general practice.

Successive answered 30/1, 2012 at 23:9 Comment(2)
I don't understand why you say "it starts to matter". Either it matters or it doesn't. Can you please elaborate?Diagenesis
@balajeerc: "It starts to matter" in English is read to mean "it matters in the latter situation but not the former". In other words, "when changing from situation A to B, importance ('mattering') goes from zero to nonzero". In straight assignment the return does not matter. Inside a conditional it matters if the thing you return is true or false, but not exactly which object it is. In the chained assignments case, you really want the return to be the current object because the results would be counterintuitive otherwise.Successive
D
11

Your assignment operator should always do these three things:

  1. Take a const-reference input (const MyClass &rhs) as the right hand side of the assignment. The reason for this should be obvious, since we don't want to accidentally change that value; we only want to change what's on the left hand side.

  2. Always return a reference to the newly altered left hand side, return *this. This is to allow operator chaining, e.g. a = b = c;.

  3. Always check for self assignment (this == &rhs). This is especially important when your class does its own memory allocation.

    MyClass& MyClass::operator=(const MyClass &rhs) {
        // Check for self-assignment!
        if (this == &rhs) // Same object?
            return *this; // Yes, so skip assignment, and just return *this.
    
        ... // Deallocate, allocate new space, copy values, etc...
    
        return *this; //Return self
    }
    
Dasilva answered 30/1, 2012 at 23:16 Comment(4)
Checking for self-assignment is a naive solution, the correct one is copy-and-swap.Sophister
Thanks for the response, but I was only trying to make a simple example by leaving out the self assignment check. I understood everything bar the returning a reference.Medieval
@MatteoItalia Copy-and-swap can be expensive. For example, assignment of one large vector to another cannot reuse the target's memory if copy-and-swap is used.Additament
Check out the reference given in the accepted answer. It tells you why passing rhs by value may be a perfectly valid option. In Copy assignment operator - cppreference.com, both options of non-trivial implementations are distinguished (your answer reflects only the second one). This unfortunately makes your seemingly simple recipe a bit misleading.Osmond
B
2

When you overload an operator and use it, what really happens at compilation is this:

Foo a, b, c;

a = b;

//Compiler implicitly converts this call into the following function call:
a.operator=(b);

So you can see that the object b of type FOO is passed by value as argument to the object a's assignment function of the same type. Now consider this, what if you wanted to cascade assignment and do something like this:

a = b = c;
//This is what the compiler does to this statement:

a.operator=(b.operator=(c));

It would be efficient to pass the objects by reference as argument to the function call because we know that NOT doing that we pass by value which makes a copy inside a function of the object which takes time and space.

The statement 'b.operator=(c)' will execute first in this statement and it will return a reference to the object had we overloaded the operator to return a reference to the current object:

Foo &operator=(const Foo& rhs);

Now our statement:

a.operator=(b.operator=(c));

becomes:

a.operator(Foo &this);

Where 'this' is the reference to the object that was returned after the execution of 'b.operator=(c)'. Object's reference is being passed here as the argument and the compiler doesn't have to create a copy of the object that was returned.

Had we not made the function to return Foo object or its reference and had made it return void instead:

void operator=(const Foo& rhs);

The statement would've become something like:

a.operator=(void);

And this would've thrown compilation error.

TL;DR You return the object or the reference to the object to cascade(chain) assignment which is:

a = b = c;
Bag answered 27/12, 2021 at 0:8 Comment(1)
If the assignment operator returns by value, calling assginment function will invoke copy constructor which will consume more time and space. What I want to ask is, will there be some other side effects, or time and space is the only side effect of returning by value other than returning by reference?Drudgery

© 2022 - 2024 — McMap. All rights reserved.