This post is a little overclocked before I enter it. I want to clearly say what I'm asking: have you added move-enabled settings to your code and have you found this useful? And how much of my behavior can I expect, maybe it depends on the compiler?
What I see here is whether it is worth adding the set-enabled setter function in cases where I set a property of a complex type. Here I have buttons with support for moving Bar
and Foo
that have the Bar
property that can 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; };
In general, in the past I went with the const-ref function for setter functions for non-built-in types. The parameters I was looking at were to pass value-value, then move ( SetByMovedCopy
), pass const-ref, then copy ( SetByConstRef
) and finally take r-value-ref and then move ( SetByMove
). As a baseline, I also included the pass-by-value, and then copied ( SetByCopy
). FWIW, the compiler complained of ambiguity if it included both forwarding values and r-value-ref overloads.
In experiments with the VS2010 compiler, this is what I 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
copied from bar_one
, then value
copied to Bar
. value
destroyed and bears any cost of destroying the complete object. 2 copy operations are performed.
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
copied from bar_one
, then value
moves to _bar
, then the gutted value
destroyed after the function exits, presumably with a 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
copied directly to _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
moves directly to _bar
. 1 move operation.
Thus, the const-ref and move versions are most effective in this case. Now, moreover, I want to do the following:
void SetBar(Bar const & value) { _bar = value; } void SetBar(Bar && value) { _bar = std::move(value); }
What I found happens here, so if you call Foo::SetBar
, the compiler selects a function based on whether you pass the value of l or the value of r. You can cause a problem 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 when I think about adding all these portable setters, but I think that this can lead to a significant increase in performance in cases where the temporary is passed to the SetBar
function and in the future I can get even more by using std::move
where necessary.