简体   繁体   中英

What is the correct way to use move semantics in C++?

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?

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.

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

  • creating lvalue reference to lvalue - OK
  • creating rvalue reference to rvalue - OK
  • creating lvalue const reference to rvalue - OK
  • creating lvalue reference to rvalue - compile ERROR

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM