Reputation: 31
Consider this code:
class A {
private:
std::string data;
public:
void set_data(std::string&& data) {
this->data = std::move(data); // line 6
}
};
int main() {
std::string move_me = "Some string";
A a;
a.set_data(std::move(move_me)); // line 13
}
I'm understand that we need to call std::move()
on line 13 so that it casts an lvalue to rvalue reference (does that sound correct? I'm new to this).
However, on line 6, do we need to use std::move()
again? I assume not, since we already passed an rvalue reference and std::string
's move constructor will be called. Is that correct?
Upvotes: 1
Views: 425
Reputation: 18864
You need it in both on line #6
and line #13
.
There is a nice post from Scott Mayers on the subject.
The most acceptable ways are
// 1: full flexibility for the caller to decide where the data state comes from
struct X
{
Y data_;
explicit X(const Y& data) : data_(data) { }
explicit X(Y&& data) : data_(std::move(data)) { }
};
// 2: forced copy or move at the call site and zero-copy move into the internal state of the X
struct X
{
Y data_;
explicit X(Y data) : data_(std::move(data)) { }
};
// 3: same as the setter below, but can have quite different forms based on what exactly is required
struct X
{
Y data_;
template <class... Z>
explicit X(Z&&... arg) : data_(std::forward<Z>(args)...) { }
}
The setter is best done in the "transparent" style delegating effectively to the assignment operator of the field.
template <typename Arg> void setData(Arg&& arg) {
data_ = std::forward<Arg>(arg);
}
I would recommend to code a simple class with all sorts of copy/move constructors/operators instrumented with debug prints and play with such class a bit to develop the intuition of how to work with &&
, std::forward
, and std::move
. That's what I did back in the days, anyway.
Upvotes: -1
Reputation: 133
It seems both answers are correct , I am just adding paragraph from the standard that explains why it's correct to use std::move()
in line #6
and line #13
and why it's is an lvalue even though the type is an rvalue in line #6
.
The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise. 5.1.1[expr.prim.general]/8
So applying this rule from the standard we can hopefully get our answers straight.
lvalue
// move_me is identifier of a variable denotes to itself the result is lvalue
std::string move_me = "Some string";
rvalue
// constructing temporary e.g no identifier is an rvalue
std::string("Some string") ;
lvalue
// the variable data has type rvalue reference to move_ms, it denotes entity move_ms
// the result is lvalue
void set_data(std::string&& data);
lvalue
// the variable data has type lvalue reference to move_ms,
//it denotes entity move_ms the result is lvalue
void set_data(std::string& data);
lvalue or rvalue - Universal references
//the variable data has type universal reference it either holds lvalue or rvalue
template<typename T> void setdata(T && data) ;
So, rvalue reference is not rvalue , things can go wrong
Base(Base const & rhs); // non-move semantics
Base(Base&& rhs); // move semantics
if you miss to use std::move()
Derived(Derived&& rhs) : Base(rhs) // wrong: rhs is an lvalue
{
// Derived-specific stuff
}
The correct version is :
Derived(Derived&& rhs) : Base(std::move(rhs)) // good, calls Base(Base&& rhs)
{
// Derived-specific stuff
}
Also
Upvotes: 1
Reputation: 13424
However, on line 6, do we need to use
std::move()
again?
Yes. Why? Because inside set_data
, data
(the argument) is an lvalue, because it has a name. Both std::move
s are necessary to actually move move_me
to data
in a
.
Without the std::move
on line 6
, move_me
would not be moved, because that would call std::string(const std::string&)
, not std::string(std::string&&)
.
Remember - if something has a name, it is an lvalue.
Upvotes: 5