I came across this answer to how to write C getters/setters and the author implies that when it comes to value-oriented properties, the setters in the standard library use the std::move()
like this...
class Foo
{
X x_;
public:
X x() const { return x_; }
void x(X x) { x_ = std::move(x); }
}
(code taken directly from the aforementioned answer)
... to leverage the move operator, if it is specified, potentially improving performance.
That itself makes sense to me - the values are copied once when passed by value to the method, so there's no need to copy them a second time to the property if they can be moved instead. However, my knowledge of C isn't good enough to be certain that doing this is safe in all cases.
Does passing an argument by value always make a deep copy? Or does it depend on the object? Considering the std::move()
supposedly "signals the compiler I don't care what happens to the moved object", this could have unexpected side effects if I intended the original object to stay.
Apologies if this is a well-known problem, I'm in the process of learning C and am really trying to get to the bottom of the language.
CodePudding user response:
Yes there is.
Receiving a parameter by value and move is okay if you always send an rvalue to that parameter. It is also okay to send an lvalue, but will be slower than receiving by const ref, especially in a loop.
Why? It seem that instead of making a copy you simply make a copy and then move, in which the move in insignificant in term of perfomance.
False.
You assume that a copy assignment is as slow as a copy constructor, which is false.
Consider this:
std::string str_target;
std::string long1 = "long_string_no_sso hello1234";
std::string long2 = "long_string_no_sso goobye123";
str_target = long1; // allocation to copy the buffer
str_target = long2; // no allocation, reuse space, simply copy bytes
This is why for setter function, by default, you should receive by const ref by default, and add a rvalue ref to optimize for rvalues:
class Foo
{
X x_;
public:
X x() const { return x_; }
// default setter, okay in most cases
void x(X const& x) { x_ = x; }
// optionally, define an overload that optimise for rvalues
void x(X&& x) { x_ = std::move(x); }
};
The only place where this does not apply is on constructor parameter and other sink functions, since they always construct and there is no buffer to reuse:
struct my_type {
// No buffer to reuse, this->_str is a totally new object
explicit my_type(std::string str) noexcept : _str{std::move(str)}
private:
std::string _str;
};