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.
SetByMovedCopy
function above. What I didn't write down but did try was to pass an r-value (e.g.SetByMovedCopy(Bar())
. In this case it does 2 move ops (1 move ctor + 1 move assignment), in the case of passing an l-value it does 1 copy ctor + 1 move assignment. Maybe c++11 compilers will do it better, but this is what VS2010 does. – TarpleySetByMovedCopy
is almost as fast as the fastest option (with optimizations on, it's usually as fast), and is doesn't touch the heap any more than necessary, all in one function. – AbeokutaSetParameter(vector<string>(1000, "test"))
) and otherwise the two methods timed similarly when passing an l-value or std::move(l-value). As far as I can tell, you're right. It is better to just use setters by-value. – Tarpley