Reputation: 41
I'm trying to better understand LValue, RValue, and how std::move works. I have the following code
#include <string>
class A
{
public:
A() = default;
A(std::string&& aString): myString(std::move(aString)) {}
std::string myString;
};
class B
{
public:
void InitMembers(std::string& aString) { myA = A(std::move(aString));}
private:
A myA;
};
int main()
{
B b;
std::string hello;
b.InitMembers(hello);
}
my questions are:
void InitMembers(string& aString) { myA = A(std::move(aString));}
I understand that I have to use std::move to aString in order to cast aString from an LValue reference to a RValue reference. But I have some doubts regarding the meaning of aString in the InitMember scope. aString is provided as an LValue reference, but in the method scope it's considered as an LValue and that's why I have to use the std::move? Std::move should rely on reference deduction (right?), how does it deduce the type in this case? It will deduce a type "string" o "string&" since aString is provided as a LValue Reference in the method's arguments?Isn't the following implementation good as the one above?
#include <string>
class A
{
public:
A() = default;
A(std::string& aString): myString(std::move(aString)) {}
std::string myString;
};
class B
{
public:
void InitMembers(std::string& aString) { myA = A(aString);}
private:
A myA;
};
int main()
{
B b;
std::string hello;
b.InitMembers(hello);
}
Thanks :)
Upvotes: 1
Views: 206
Reputation: 28416
Concerning
A(std::string&& aString): myString(std::move(aString)) {}
std::string&&
denotes an rvalue reference to a std::string
. Rvalue references only bind to rvalues (both prvalues and xvalues), so the two possible call sites will be like this:
// assuming this is defined somewhere
std::string f(void); // returns by value, i.e. `f()` is an rvalue
// call site 1
A a1{f()}; // `f()` is an rvalue (precisely a prvalue)
// assuming this
std::string s{"ciao"}; // s is an lvalue
// call site 2
A a2{std::move(s)}; // `std::move(s)` is an rvalue (precisely an xvalue)
// i.e. with `std::move` we turn s into an rvalue argument,
// so it can bind to the rvalue reference parameter
// don't expect s to be the one it was before constructing a2
In either case, what does the constructor do with aString
?
Well, aString
is an lvalue, because "it has a name" (the actual definition is a bit more complicated, but this is the easiest one to get started with, and it isn't all that wrong after all), so if you use it verbatim, the compiler won't assume it is bound to a temporary and it won't let myString
steal it resources.
But you know that aString
is bound to a temporary, because you've declared it as std::string&&
, so you pass it as std::move(aString)
to tell the compiler "treat this is a temporary".
Yes, technically also the compiler knows that aString
is bound to a temporary, but it can't std::move
it automatically. Why? Because you might want to use it more than once:
A(std::string&& aString) : myString(aString/* can't move this */) {
std::cout << aString << std::endl; // if I want to use it here
}
// yes, this is a very silly example, sorry
As regards
void InitMembers(std::string& aString) { myA = A(std::move(aString));}
aString
denotes an lvalue reference to non-const
std::string
, so you can pass to .InitMembers
only non-const
lvalues.
Then inside the function you're std::move
ing it to tell A
's constructor "look, this is a temporary". But that also means that at the call site (b.InitMembers(hello);
) you're leaving the input (hello
) in a moved-from state, just like the s
in the first example above. That's ok, because the caller knows that InitMembers
takes its parameter by non-const
lvalue reference, so it is aware that the argument they pass can be changed by the call. Just like in the previous example it's the user who's writing std::move
around s
, so they're supposed to know what they do.
For more details about how std::move
works (and std::forward
as well), I want to point you to this answer of mine.
Upvotes: 1