Why must the copy assignment operator return a reference/const reference?
Strictly speaking, the result of a copy assignment operator doesn't need to return a reference, though to mimic the default behavior the C++ compiler uses, it should return a non-const reference to the object that is assigned to (an implicitly generated copy assignment operator will return a non-const reference - C++03: 12.8/10). I've seen a fair bit of code that returns void
from copy assignment overloads, and I can't recall when that caused a serious problem. Returning void
will prevent users from 'assignment chaining' (a = b = c;
), and will prevent using the result of an assignment in a test expression, for example. While that kind of code is by no means unheard of, I also don't think it's particularly common - especially for non-primitive types (unless the interface for a class intends for these kinds of tests, such as for iostreams).
I'm not recommending that you do this, just pointing out that it's permitted and that it doesn't seem to cause a whole lot of problems.
These other SO questions are related (probably not quite dupes) that have information/opinions that might be of interest to you.
- Has anyone found the need to declare the return parameter of a copy assignment operator const?
- Overloading assignment operator in C++
Why should the assignment operator return a reference to the object?
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.
Why does operator = return *this?
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=(...)
.
Why do we return *this in asignment operator and generally (and not &this) when we want to return a reference to the object?
this
is a pointer that keeps the address of the current object. So dereferencing the pointer like *this
you will get the lvalue of the current object itself. And the return type of the copy assignment operator of the presented class is A&
. So returning the expression *this
you are returning a reference to the current object.
According to the C++ 17 Standard (8.1.2 This)
1 The keyword this names a pointer to the object for which a
non-static member function (12.2.2.1) is invoked or a non-static data
member’s initializer (12.2) is evaluated.
Consider the following code snippet as an simplified example.
int x = 10;
int *this_x = &x;
Now to return a reference to the object you need to use the expression *this_x
as for example
std::cout << *this_x << '\n';
Why we use reference return in assignment operator overloading and not at plus-minus ops?
Returning a reference from assignment allows chaining:
a = b = c; // shorter than the equivalent "b = c; a = b;"
(This would also work (in most cases) if the operator returned a copy of the new value, but that's generally less efficient.)
We can't return a reference from arithmetic operations, since they produce a new value. The only (sensible) way to return a new value is to return it by value.
Returning a constant value, as your example does, prevents move semantics, so don't do that.
Why move assignment operator should return reference to *this
Returning Foo
is potentially expensive, and impossible if the type isn't copyable. It's also surprising: (a=b)=c
would create a temporary and assign c
to that, when you'd expect both assignments to be to a
.
Returning Foo&&
is just weird; you don't want things mysteriously turning into rvalues so that e.g. f(a=b)
unexpectedly moves from a
without you telling it to.
Returning Foo&
is the conventional way to make the operator behave in an unsurprising way if you chain it.
Assignment operator overloading: returning void versus returning reference parameter
Although C++ language lets you overload assignment operator with any return type, including void
, you should strongly consider following a widespread convention of returning a reference to the assignee from the operator.
The rationale for it is that
A = B;
will work no matter what the assignment returns, while
A = B = C;
which is a perfect chain of assignments will break, unless B = C
returns something assignment-compatible to A
(which is usually an object of the same type as A
).
Another problem is in situations when you must compare the object as part of a larger expression, for example
mytype obj;
while ((obj = read_obj(cin)) != END_OBJ) {
...
}
Hence, the biggest drawback to returning void
is inability to chain assignments and use them in places where void
is not allowed.
Should the assignment operator observe the assigned object's rvalueness?
IMHO, the original suggestion by Dietmar Kühl (providing overloads for &
and &&
ref-qualifiers) is superior than Simple's one (providing it only for &
).
The original idea is:
class G {
public:
// other members
G& operator=(G) & { /*...*/ return *this; }
G operator=(G) && { /*...*/ return std::move(*this); }
};
and Simple has suggested to remove the second overload. Both solutions invalidate this line
G& g = G() = G();
(as wanted) but if the second overload is removed, then these lines also fail to compile:
const G& g1 = G() = G();
G&& g2 = G() = G();
and I see no reason why they shouldn't (there's no lifetime issue as explained in Yakk's post).
I can see only one situation where Simple's suggestion is preferable: when G
doesn't have an accessible copy/move constructor. Since most types for which the copy/move assignment operator is accessible also have an accessible copy/move constructor, this situation is quite rare.
Both overloads take the argument by value and there are good reasons for that if G
has an accessible copy/move constructor. Suppose for now that G
does not have one. In this case the operators should take the argument by const G&
.
Unfortunately the second overload (which, as it is, returns by value) should not return a reference (of any type) to *this
because the expression to which *this
binds to is an rvalue and thus, it's likely to be a temporary whose lifetime is about to expiry. (Recall that forbidding this from happening was one of the OP's motivation.)
In this case, you should remove the second overload (as per Simple's suggestion) otherwise the class doesn't compile (unless the second overload is a template that's never instantiated). Alternatively, we can keep the second overload and define it as delete
d. (But why bother since the existence of the overload for &
alone is already enough?)
A peripheral point.
What should be the definition of operator =
for &&
? (We assume again that G
has an accessible copy/move constructor.)
As Dietmar Kühl has pointed out and Yakk has explored, the code of the both overloads should be very similar and, in this case, it's better to implement the one for &&
in terms of the one for &
. Since the performance of a move is expected to be no worse than a copy (and since RVO doesn't apply when returning *this
) we should return std::move(*this)
. In summary, a possible one-line definition is:
G operator =(G o) && { return std::move(*this = std::move(o)); }
This is good enough if only G
can be assigned to another G
or if G
has (non-explicit) converting constructors. Otherwise, you should instead consider giving G
a (template) forwarding copy/move assignment operator taking an universal reference:
template <typename T>
G operator =(T&& o) && { return std::move(*this = std::forward<T>(o)); }
Although this is not a lot of boiler plate code it's still an annoyance if we have to do that for many classes. To decrease the amount of boiler plate code we can define a macro:
#define ASSIGNMENT_FOR_RVALUE(type) \
template <typename T> \
type operator =(T&& b) && { return std::move(*this = std::forward<T>(b)); }
Then inside G
's definition one adds ASSIGNMENT_FOR_RVALUE(G)
.
(Notice that the relevant type appears only as the return type. In C++14 it can be automatically deduced by the compiler and thus, G
and type
in the last two code snippets can be replaced by auto
. It follows that the macro can become an object-like macro instead of a function-like macro.)
Another way of reducing the amount of boiler plate code is defining a CRTP base class that implements operator =
for &&
:
template <typename Derived>
struct assignment_for_rvalue {
template <typename T>
Derived operator =(T&& o) && {
return std::move(static_cast<Derived&>(*this) = std::forward<T>(o));
}
};
The boiler plate becomes the inheritance and the using declaration as shown below:
class G : public assignment_for_rvalue<G> {
public:
// other members, possibly including assignment operator overloads for `&`
// but taking arguments of different types and/or value category.
G& operator=(G) & { /*...*/ return *this; }
using assignment_for_rvalue::operator =;
};
Recall that, for some types and contrarily to using ASSIGNMENT_FOR_RVALUE
, inheriting from assignment_for_rvalue
might have some unwanted consequences on the class layout.
Related Topics
May Std::Vector Make Use of Small Buffer Optimization
Why Don't the C or C++ Standards Explicitly Define Char as Signed or Unsigned
C++ Return Value Created Before or After Auto Var Destruction
C++ Qt Signal and Slot Not Firing
Variable Declarations in Header Files - Static or Not
Lambda Implicit Capture Fails with Variable Declared from Structured Binding
Accessing a Protected Member of a Base Class in Another Subclass
Why Isn't a For-Loop a Compile-Time Expression
Std::Optional Specialization for Reference Types
C++: Safe to Use Longjmp and Setjmp
How to Use String.Substr() Function
Why Should Default Parameters Be Added Last in C++ Functions
Explain C++ Sfinae to a Non-C++ Programmer
Problems with Singleton Pattern
Why Is "Using Namespace X;" Not Allowed at Class/Struct Level