I recently read (and unfortunately forgot where), that the best way to write operator= is like this:
foo &operator=(foo other)
{
swap(*this, other);
return *this;
}
instead of this:
foo &operator=(const foo &other)
{
foo copy(other);
swap(*this, copy);
return *this;
}
The idea is that if operator= is called with an rvalue, the first version can optimize away construction of a copy. So when called with a rvalue, the first version is faster and when called with an lvalue the two are equivalent.
I'm curious as to what other people think about this? Would people avoid the first version because of lack of explicitness? Am I correct that the first version can be better and can never be worse?
In C++, a Copy Constructor may be called in the following cases: When an object of the class is returned by value. When an object of the class is passed (to a function) by value as an argument. When an object is constructed based on another object of the same class.
The explicit function specifier controls unwanted implicit type conversions. It can only be used in declarations of constructors within a class declaration. For example, except for the default constructor, the constructors in the following class are conversion constructors.
An implicitly defined copy constructor will copy the bases and members of an object in the same order that a constructor would initialize the bases and members of the object.
In programming, implicit is often used to refer to something that's done for you by other code behind the scenes. Explicit is the manual approach to accomplishing the change you wish to have by writing out the instructions to be done explicitly.
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.
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");
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With