This post rambles a bit so before I get into it I want to be clear what I'm asking: Have you added move-enabled setters to your code and have you found it's worth the effort? And how much of the behaviour I've found can I expect might be compiler-specific?
What I'm looking at here is whether it's worth while to add a move-enabled setter function in cases where I'm setting a property of a complex type. Here I have move-enabled Bar and Foo which has a Bar property which may be set.
class Bar {
public:
Bar() : _array(1000) {}
Bar(Bar const & other) : _array(other._array) {}
Bar(Bar && other) : _array(std::move(other._array)) {}
Bar & operator=(Bar const & other) {
_array = other._array;
return *this;
}
Bar & operator=(Bar && other) {
_array = std::move(other._array);
return *this;
}
private:
vector<string> _array;
};
class Foo {
public:
void SetBarByCopy(Bar value) {
_bar = value;
}
void SetBarByMovedCopy(Bar value) {
_bar = std::move(value);
}
void SetBarByConstRef(Bar const & value) {
_bar = value;
}
void SetBarByMove(Bar && value) {
_bar = std::move(value);
}
private:
Bar _bar;
};
Generally speaking in the past I've gone with a const-ref for setter functions for non built-in types. The options I looked at were to pass by-value then move (SetByMovedCopy), pass by const-ref then copy (SetByConstRef) and finally to accept by r-value-ref then move (SetByMove). As a baseline I also included pass-by-value then copy (SetByCopy). FWIW, the compiler complained of ambiguity if including both pass-by-value and r-value-ref overloads.
In experiments with the VS2010 compiler, this is what I've found:
Foo foo;
Bar bar_one;
foo.SetByCopy(bar_one);
// Bar::copy ctor called (to construct "value" from bar_one)
// Foo::SetByCopy entered
// Bar::copy operator= called (to copy "value" to _bar)
// Foo::SetByCopy exiting
// Bar::dtor called (on "value")
value is copy-constructed from bar_one, then value is copied to bar. value is destructed and incurs any costs of destructing a complete object. 2 copy operations are executed.
foo.SetByMovedCopy(bar_one);
// Bar::copy ctor called (to construct "value" from bar_one)
// Foo::SetByCopy entered
// Bar::move operator= called (to move "value" into _bar)
// Foo::SetByCopy exiting
// Bar::dtor called (to destruct the moved "value")
value is copy-constructed from bar_one, then value is moved into _bar, then the gutted value is destructed after the function exits, presumably at lower cost. 1 copy and 1 move operation.
foo.SetByConstRef(bar_one);
// Foo::SetByConstRef entered
// Bar::copy operator= called (to copy bar_one into _bar)
// Foo::SetByConstRef exiting
bar_one is copied directly into _bar. 1 copy operation.
foo.SetByMove(std::move(bar_one))
// Foo::SetByMove entered
// Bar::move operator= called (to move "value" into _bar)
// Foo::SetByMove exited
bar_one is moved directly into _bar. 1 move operation.
So the const-ref and move versions are the most efficient in this case. Now, more to the point, what I'm looking to do is something like this:
void SetBar(Bar const & value) { _bar = value; }
void SetBar(Bar && value) { _bar = std::move(value); }
What I've found happens here is that if you call Foo::SetBar, the compiler picks the function based on whether you're passing an l-value or an r-value. You can force the issue by calling std::move as such:
foo.SetBar(bar_one); // Const-ref version called
foo.SetBar(Bar()); // Move version called
foo.SetBar(std::move(bar_one)); // Move version called
I shudder to think of adding all these move setters, but I think it might result in a pretty significant performance gain in cases where a temporary is passed to the SetBar function and going forward I can gain even more by applying std::move where appropriate.