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?

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
maccard
  • 1,198
  • 4
  • 17
  • 34
  • The correct way is whatever way implements the semantics you want; the _idiomatic_ way is certainly to return `T&` (`Foo&` in your example). – ildjarn Jan 30 '12 at 23:08
  • @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 Jan 30 '12 at 23:12
  • 1
    possible duplicate of [assignment operator return a reference to *this in C++](http://stackoverflow.com/questions/5669813/assignment-operator-return-a-reference-to-this-in-c); also [Returning *this with an assignment operator](http://stackoverflow.com/questions/3248469/returning-this-with-an-assignment-operator) – Rob Kennedy Jan 30 '12 at 23:26

4 Answers4

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.

Community
  • 1
  • 1
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • 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. – maccard Jan 30 '12 at 23:20
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.

Borealid
  • 95,191
  • 9
  • 106
  • 122
  • 1
    I don't understand why you say "it starts to matter". Either it matters or it doesn't. Can you please elaborate? – balajeerc Jun 20 '15 at 06:15
  • 3
    @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. – Borealid Jun 23 '15 at 19:42
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
    }
    
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
DotNetUser
  • 6,494
  • 1
  • 25
  • 27
  • 2
    Checking for self-assignment is a naive solution, the correct one is copy-and-swap. – Matteo Italia Jan 30 '12 at 23:18
  • 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. – maccard Jan 30 '12 at 23:23
  • 1
    @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. – Sebastian Redl Apr 01 '16 at 10:04
  • 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*](https://en.cppreference.com/w/cpp/language/copy_assignment "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. – Wolf Aug 29 '22 at 09:13
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;
cigien
  • 57,834
  • 11
  • 73
  • 112
Dayyan Ali
  • 29
  • 3
  • 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? – Lake_Lagunita Jan 29 '23 at 01:44