I am trying to understand move semantics, rvalue references, std::move
, etc. I have been trying to figure out, by searching through various questions on this site, why passing a const std::string &name
+ _name(name)
is less recommended than a std::string name
+ _name(std::move(name))
if a copy is needed.
If I understand correctly, the following requires a single copy (through the constructor) plus a move (from the temporary to the member):
Dog::Dog(std::string name) : _name(std::move(name)) {}
The alternative (and old-fashioned) way is to pass it by reference and copy it (from the reference to the member):
Dog::Dog(const std::string &name) : _name(name) {}
If the first method requires a copy and move both, and the second method only requires a single copy, how can the first method be preferred and, in some cases, faster?
If you're calling a function that needs to take a large object as a parameter, pass it by const reference to avoid making an unnecessary copy of that object and taking a large efficiency hit. If you're writing a copy or move constructor which by definition must take a reference, use pass by reference.
Unlike in C, where passing by reference was really just passing a pointer by value, in C++ we can genuinely pass by reference.
By definition, pass by value means you are making a copy in memory of the actual parameter's value that is passed in, a copy of the contents of the actual parameter.
Pass by reference allows us to pass arguments to a function without making copies of those arguments each time the function is called.
When consuming data, you'll need an object you can consume. When you get a std::string const&
you will have to copy the object independent on whether the argument will be needed.
When the object is passed by value the object will be copied if it has to be copied, i.e., when the object passed is not a temporary. However, if it happens to be a temporary the object may be constructed in place, i.e., any copies may have been elided and you just pay for a move construction. That is, there is a chance that no copy actually happens.
Consider calling the various options with an lvalue and with an rvalue:
Dog::Dog(const std::string &name) : _name(name) {}
Whether called with an lvalue or rvalue, this requires exactly one copy, to initialize _name
from name
. Moving is not an option because name
is const
.
Dog::Dog(std::string &&name) : _name(std::move(name)) {}
This can only be called with an rvalue, and it will move.
Dog::Dog(std::string name) : _name(std::move(name)) {}
When called with an lvalue, this will copy to pass the argument and then a move to populate the data member. When called with an rvalue, this will move to pass the argument, and then move to populate the data member. In the case of the rvalue, moving to pass the argument may be elided. Thus, calling this with an lvalue results in one copy and one move, and calling this with an rvalue results in one to two moves.
The optimal solution is to define both (1)
and (2)
. Solution (3)
can have an extra move relative to the optimum. But writing one function is shorter and more maintainable than writing two virtually identical functions, and moves are assumed to be cheap.
When calling with a value implicitly convertible to string like const char*
, the implicit conversion takes place which involves a length computation and a copy of the string data. Then we fall into the rvalue cases. In this case, using a string_view
provides yet another option:
Dog::Dog(std::string_view name) : _name(name) {}
When called with a string lvalue or rvalue, this results in one copy. When called with a const char*
, one length computation takes place and one copy.
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