18

Say I want to override the operator = so I can do something like

Poly p1;  // an object representing a polynomial
Poly p2;  // another object of the same type    
p2 = p1;  // assigns all the contents of p1 to p2

Then in my implementation of the operator =, I have something like this:

Poly& Poly::operator=(const Poly &source) {
    // Skipping implementation, it already works fine…
    return *this;
}

Don't mind the implementation, it already works fine.

My concern is that what happens when you return *this? I know it returns a reference to the object but is this what happens?

p2 = &p1
5gon12eder
  • 24,280
  • 5
  • 45
  • 92
Jude Maranga
  • 865
  • 2
  • 9
  • 27
  • 1
    Since the `I am just new to C++` part has been lost in an (otherwise well meant) edit, and since variations of `p2 = &p1` resurfaced in the comments... The `&` in the `Poly& Poly::operator=` declaration means that the operator returns a reference - to itself in this case, which is precisely what `return *this;` does. On the other hand, the `&` in `&p1` evaluates to the address of `p1`, which is a `Poly*` pointer, and cannot be assigned to a `Poly` object. Do not confuse the two meanings of `&` in the two different contexts. – dxiv Jan 02 '16 at 05:56

5 Answers5

29

You return *this so you can write normal compound C++ = statements like:

Poly p1; //an object representing a polynomial
Poly p2;
Poly p2;

// ...

p3 = p2 = p1;  //assigns all the contents of p1 to p2 and then to p3

because that statement is basically:

p3.operator=(p2.operator=(p1));

If p2.operator=(...) didn't return *this you'd have nothing meaningful to pass into p3.operator=(...).

Paul Evans
  • 27,315
  • 3
  • 37
  • 54
  • yes I know that. But my concern is what happens when you return *this? I mean, basing from your example, does it work like this? p3 = p2 = &p1; p3 = &p2; – Jude Maranga Jan 02 '16 at 05:27
  • @JudeMaranga Edited answer accordingly. You don't want to take the address of, say `p1` (*i.e.* `&p1`), `operator=` takes a `const Poly &source` so `*this` gives you that. – Paul Evans Jan 02 '16 at 05:29
  • I get what you're trying to say in there. but after p3.operator=(...) return *this, who would be catching that return value? – Jude Maranga Jan 02 '16 at 05:31
  • 1
    @JudeMaranga It just the same as `42;` being a valid C++ statement. The value is thrown away. Think about `cout << "C++ throws unused " << "return values away" << endl`; – Paul Evans Jan 02 '16 at 05:33
  • Nothing is catching the `p3` return, but that's fine. Paul's explanation is correct. No worse than `(void) sqrt(9.0)` [useless, but legal]. – Craig Estey Jan 02 '16 at 05:33
  • @CraigEstey so sir, if I don't want to have nested assignments, would it just be fine if I make the return value of my operator = as void? – Jude Maranga Jan 02 '16 at 05:36
  • @JudeMaranga The technique might be more familiar to you in constructs like `thing.doSomething().with(thisThing).andAlso(thatThing).fast().andNot().slow()` which is a more awkward syntax for basically the same thing. – 5gon12eder Jan 02 '16 at 05:38
  • 1
    @JudeMaranga That's only reason for the `return *this`. So yes, you don't need if **you're never going to use nested assignment**. But it's a convention and best stuck to. If you don't you know you're going to get bitten at some point in the future. – Paul Evans Jan 02 '16 at 05:38
  • @PaulEvans Maybe you can answer [better] Jude's question to me. But, I'd say, that you're only talking about a single extra `mov xxx,%eax` (e.g.) and I think you also have to conform to what the `=` operator requires. AFAIK, you don't get to choose an alternate [incompatible] return type. You can't predict the calling context. Just relax and do the return :-) – Craig Estey Jan 02 '16 at 05:42
  • @PaulEvans I actually disagree that this is the *only reason*. Another application is directly using the result of an assignment as is frequently done in the condition of an `if` or `while` statement. – 5gon12eder Jan 02 '16 at 05:43
  • @5gon12eder Yes, that's correct. But surely we don't write assignments in boolean expressions. `if ( x = y ...` being very bad form. – Paul Evans Jan 02 '16 at 05:46
  • How about [the equally montrous equivalent]: `if ((f = fopen(...)) != NULL)`? [I _never_ do that, but it's ubiquitous] – Craig Estey Jan 02 '16 at 05:48
  • I think that `while (p = p.derivative()) { … }` would be very reasonable code (assuming the conversion operator to `bool` returns `true` for any non-null polynomial). You can add an extra layer of parenthesis if you prefer. – 5gon12eder Jan 02 '16 at 05:49
  • @CraigEstey Yes, there's load of &%$! that's ubiquitous. Doesn't mean we can't do better :) ) – Paul Evans Jan 02 '16 at 05:51
  • @5gon12eder Hmmm, I'd rather avoid the possibility that the `=` is mistaken for `==` (or even *changed* to `==` in a later edit!)). But I see your point, `while` loops can be tricky to get right without too much noise. – Paul Evans Jan 02 '16 at 05:52
  • You could put a comment `// single = is intentional here` – Benjamin Lindley Jan 02 '16 at 07:26
10

p2 = p1 is a shorthand for p2.operator=(p1). It is just calling your operator= function, which is returning a reference to p2, which you are then ignoring. To make this clear, let's call it assign instead of operator=:

Poly& Poly::assign(const Poly &source) {
    .
    .
    .
    return *this;
}

Now instead of p2 = p1, you would write

p2.assign(p1);

In this case, the result of calling assign is being ignored, but you don't have to ignore it. For example, you could write:

p3.assign(p2.assign(p1));

Using operator= instead of assign, this becomes

p3 = (p2 = p1);

but since assignment is right-associative, this can also be written as

p3 = p2 = p1;

This form of being able to do multiple assignments at once originally comes from C and has been preserved in C++ through the convention of returning *this in operator=().

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
7

One might be tempted to make the copy-assignment operator return void if you won't ever need chained assignments (as shown in the other answers) anyway. After all, chained assignments are often hard to read and understand, so not allowing them might be considered an improvement.

However, an often overlooked aspect is that void operator=(Poly& const) means that your type would no longer fulfuill the CopyAssignable concept, which requires a T& return type.

A type which does not fulfill the CopyAssignable concept cannot be officially used for some standard-container operations, for example std::vector::insert, which means that the following seemingly innocent piece of code yields undefined behaviour, even though it probably runs perfectly fine:

#include <vector>

struct Poly
{
    void operator=(Poly const&) {} // Poly is not CopyAssignable
};

int main()
{
  std::vector<Poly> v;
  Poly p;
  v.insert(v.begin(), p); // undefined behaviour
}

As the C++ standard explains in § 17.6.4.8/2.3 where it talks about constraints on programs using the standard library:

(...) the effects are undefined in the following cases:

(...) for types used as template arguments when instantiating a template component, if the operations on the type do not implement the semantics of the applicable Requirements subclause (...).

Of course, it's precisely because of the undefined behaviour that a compiler is allowed to ignore the error and make the program behave nicely, matching the obviously intended behaviour. But it's not required to do so.

You should also consider that you cannot predict all future uses of your Poly type. Someone might write some template function such as:

template <class T>
void f(T const& t)
{
    T t2;
    T t3 = t2 = t;
    // ...
}

This function would then not work with your Poly class.

Just don't violate this C++ convention and you won't run into troubles.

F14
  • 67
  • 2
  • 6
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
3

what happens when you return *this?

In your example (p2 = p1;), nothing. The method copies p1 into p2 and returns a reference to the 'this' object, which the calling code doesn't use.

In code such as p3 = p2 = p1;, the first invocation is p2 = p1, which copies p1 into p2 and returns a reference to p2. The calling code then copies from that reference-to-p2 into p3 (and ignores the reference to p3 that is returned).

(In passing: do your unit tests ensure that p1 = p1 works properly? It's easy to forget that case!)

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • 1
    If you implement the operator correctly, using the copy-and-swap idiom, then you don't need to handle self-assignment at all. Indeed, `if (this != &other)` is often an indicator of dangerous code. An exception may be made for rare optimisation issues. – Christian Hackl Jan 07 '16 at 20:07
0

Returning a reference to the target object allow assignment chaining (cascading), and overloading operators within a class follows right-associative (click here for detailed operator overloading rules)

Poly a, b, c;
a = b = c;