25
votes

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?

4
The correct way is whatever way implements the semantics you want; the idiomatic way is certainly to return T& (Foo& in your example).ildjarn
@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.maccard

4 Answers

24
votes

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.

17
votes

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.

9
votes

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
    }
    
0
votes

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;