I recently read (and unfortunately forgot where), that the best way to write operator= is like this:
foo &operator=(foo other)
{
swap(*this, other);
You probably read it from: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
I don't have much to say since I think the link explains the rationale pretty well. Anecdotally I can confirm that the first form results in fewer copies in my builds with MSVC, which makes sense since compilers might not be able to do copy-elision on the second form. I agree that the first form is a strict improvement and is never worse than the second.
Edit: The first form might be a bit less idiomatic, but I don't think it's much less clear. (IMO, it's not any more surprising than seeing the copy-and-swap implementation of the assignment operator for the first time.)
Edit #2: Oops, I meant to write copy-elision, not RVO.
Given this
foo &foo::operator=(foo other) {/*...*/ return *this;}
foo f();
in code like this
foo bar;
bar = f();
it might be easier for a compiler to eliminate the call to the copy constructor. With RVO, it could use the address of the operator's other
parameter as the place for f()
to construct its return value at.
It seems that this optimization is also possible for the second case, although I believe it might be harder. (Especially when the operator isn't inlined.)
I think that you might be confusing the difference between:
foo &operator=(const foo &other);
and
const foo &operator=(const foo &other);
The first form should be used to allow for: (a = b) = c;
These two are actually the same. The only difference is where you press "Step In" in a debugger. And you should know in advance where to do it.
I generally prefer the second one from readability and 'least surprise' point of view, however I do acknowledge that the first one can be more efficient when the parameter is a temporary.
The first one really can lead to no copies, not just the single copy and it's conceivable that this may be a genuine concern in extreme situations.
E.g. Take this test program. gcc -O3 -S
(gcc version 4.4.2 20091222 (Red Hat 4.4.2-20) (GCC)) generates one call to B's copy constructor but no calls to A's copy constructor for the function f
(the assignment operator is inlined for both A
and B
). A
and B
can both be taken to be very basic string classes. Allocation and copying for data
would occur in the constructors and deallocation in the destructor.
#include <algorithm>
class A
{
public:
explicit A(const char*);
A& operator=(A val) { swap(val); return *this; }
void swap(A& other) { std::swap(data, other.data); }
A(const A&);
~A();
private:
const char* data;
};
class B
{
public:
explicit B(const char*);
B& operator=(const B& val) { B tmp(val); swap(tmp); return *this; }
void swap(B& other) { std::swap(data, other.data); }
B(const B&);
~B();
private:
const char* data;
};
void f(A& a, B& b)
{
a = A("Hello");
b = B("World");
}