C++: Std::Move with Rvalue Reference Is Not Moving Contents

C++: std::move with rvalue reference is not moving contents

explicit Trade(vecstr_t&& vec) : _vec(vec)
{}

In the constructor above, even though vec is of type rvalue reference to vecstr_t, it is itself an lvalue. The basic rule to remember is - if it has a name, it's an lvalue.

There are very few contexts where an lvalue may automatically be moved from (such as the return statement of a function that returns an object by value), but a constructor's mem-initializer list is not one of them.

In your example, _vec is copy constructed from vec. If you want it to be move constructed instead, use std::move.

explicit Trade(vecstr_t&& vec) : _vec(std::move(vec))
{}

Now the second call to print will not print anything. Note that technically the second call could print a non-zero size because the contents of a moved from vector are unspecified. But on most (probably all) implementations, you'll see an empty vector.

Live demo


Your comment below says your intent is to accept both rvalues and lvalues, move only in the case of the former, and copy the argument otherwise. As currently written, your constructor will only accept rvalues, and not lvalues. There are a few different options to achieve what you want.

The easiest probably is to change the parameter so that it's taking the argument by value, and then unconditionally move.

explicit Trade(vecstr_t vec) : _vec(std::move(vec))
{}

The drawback with this approach is that you may incur an additional move construction of the vector, but move constructing a vector is cheap, and you should go with this option in most cases.

The second option is to create two overloads of the constructor

explicit Trade(vecstr_t&&      vec) : _vec(std::move(vec)) {}
explicit Trade(vecstr_t const& vec) : _vec(vec) {}

The drawback with this one is that the number of overloads will increase exponentially as the number of constructor arguments increases.

The third option is to use perfect forwarding.

template<typename V>
explicit Trade(V&& vec) : _vec(std::forward<V>(vec)) {}

The code above will preserve the value category of the argument passed to the constructor when it forwards it to construct _vec. This means that if vec is an rvalue, the vecstr_t move constructor will be called. And if it is an lvalue, it will be copied from.

The drawback with this solution is that your constructor will accept any type of argument, not just a vecstr_t, and then the move/copy construction in the mem-initializer list will fail if the argument is not convertible to vecstr_t. This may result in error messages that are confusing to the user.

Why does rvalue object does not get moved to function with rvalue parameter?

std::move by itself does not actually move anything. That is to say, std::move alone does not invoke any move constructors or move-assignment operators. What it actually does is effectively cast its argument into an rvalue as if by static_cast<typename std::remove_reference<T>::type&&>(t), according to cppreference.

In order for any moving to actually happen, the moved object must be assigned to or used in the move-initialization of something else. For example, it could be used to initialize a member.

void something::consume_ptr(std::shared_ptr<int> && ptr) {
this->ptr = std::move(ptr);
std::cout << "Consumed " << (void*) ptr.get() << std::endl;
}

However, one way to make your pointer get moved without being assigned to anything is to simply pass it by value, causing your pointer to be moved into the parameter.

void consume_ptr(std::shared_ptr<int> ptr) {
std::cout << "Consumed " << (void*) ptr.get() << std::endl;
}

This way can actually be more useful than the rvalue way if you're going to end up assigning the parameter to something, because it allows you to pass stuff in by copy, too, and not just by move.

void consume_ptr_by_rvalue(std::shared_ptr<int> && ptr);
void consume_ptr_by_value(std::shared_ptr<int> ptr);

void do_stuff() {
std::shared_ptr<int> x = /*...*/;
std::shared_ptr<int> y = /*...*/;

// consume_ptr_by_rvalue(x); // Doesn't work
consume_ptr_by_rvalue(std::move(y)); // Risk of use-after-move

std::shared_ptr<int> z = /*...*/;
std::shared_ptr<int> w = /*...*/;

consume_ptr_by_value(z);
consume_ptr_by_value(std::move(w)); // Still risk, but you get the idea
consume_ptr_by_value(make_shared_ptr_to_something()); // Can directly pass result of something
}

Why does std::move copy contents for a rvalue or const lvalue function argument?

Your vector is actually copied, not moved. The reason for this is, although declared as an rvalue reference, vec_ denotes an lvalue expression inside the function body. Thus the copy constructor of std::vector is invoked, and not the move constructor. The reason for this is, that vec_ is now a named value, and rvalues cannot have names, so it collapses to an lvalue. The following code will fail to compile because of this reason:

void foo(int&& i)
{
int&& x = i;
}

In order to fix this issue, you have to make vec_ nameless again, by calling std::move(vec_).

std::move Not Working on RValue Reference Function

std::move only casts to Rvalue reference.

foo takes Rvalue ref to vector<int>. By move(vecNumbers) you get vector<int>&&. Inside foo you just access vecNumbers which is defined in main. You didn't do any action which changed the content of this vector.

If you really want to move (steal) content of vecNumbers you have to call either move constructor or move assignment operator. Inside foo you could do this in this way:

void foo(  std::vector<int>&& value)
{
std::vector<int> v1{std::move(value)}; // invoke move ctor which steals content of value
std::cout<<"size in Function:"<<value.size()<<"\n";
}

or you can change signature of foo to be:

void foo(std::vector<int> value) {

}

then when you call

foo(std::move(vecNumbers))

move constructor of vector<T> is called which moves vecNumbers to value inside foo.

What happens when std::move is called on a rvalue reference?

string constructed = s;

This does not cause a move because s is not an rvalue. It is an rvalue reference, but not an rvalue. If it has a name, it is not an rvalue. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression.

string constructed = std::move(s);

This causes a move because std::move(s) is an rvalue: it's a temporary and its type is not lvalue reference.

There are no other moves in the program (std::move is not a move, it's a cast).

why using std::move and assign to rvalue does not steal internal content?

It is because c is declared as a reference (rvalue reference) to a. It is not a different string. In order to "still" (i.e. call the move constructor), c needs to be declared a string. Then, string a is "moved" to string c:

int main()
{
string a="4";
string c = move(a);
cout<< "a :" <<a << ":" <<endl;

cout << "c :" << c << ":"<< endl;
}

Output is:

a ::                                                                                                                                           
c :4:

This code moves an rvalue to an lvalue though, which is the way it's supposed to work. However, it sounds like you're trying to move an lvalue to an rvalue, but that is not what you're doing. c is an rvalue, but move(a) is also an rvalue. std::move() casts a to an rvalue reference. Think of a reference as something similar to a pointer, it is not a string. You can't move or copy a string to it. It just refers to the string. Anyway, I don't think that you can move lvalues to rvalues. I can't think of any case.

Why do I have to call move on an rvalue reference?

As skypjack correctly comments, accessing an object through its name always results in an lvalue reference.

This is a safety feature and if you think it through you will realise that you are glad of it.

As you know, std::move simply casts an l-value reference to an r-value reference. If we use the returned r-value reference immediately (i.e. un-named) then it remains an r-value reference.

This means that the use of the r-value can only be at the point in the code where move(x) is mentioned. From a code-reader's perspective, it's now easy to see where x's state became undefined.

so:

 1: auto x = make_x();
2: auto&& r = std::move(x);
3: // lots of other stuff
35: // ...
54: // ...
55: take_my_x(r);

does not work. If it did, someone maintaining the code would have a hard time seeing (and remembering) that x (defined on line 1) enters an undefined state on line 55 through a reference taken on line 2.

This is a good deal more explicit:

 1: auto x = make_x();
2: //
3: // lots of other stuff
35: // ...
54: // ...
55: take_my_x(std::move(x));

Rvalue reference: Why aren't rvalues implicitly moved?

Consider this scenario:

void foo(std::string x) {}
void bar(std::string y) {}

void test(std::string&& str)
{
// to be determined
}

We want to call foo with str, then bar with str, both with the same value. The best way to do this is:

foo(str); // copy str to x
bar(std::move(str)); // move str to y; we move it because we're done with it

It would be a mistake to do this:

foo(std::move(str)); // move str to x
bar(std::move(str)); // move str to y...er, except now it's empty

Because after the first move the value of str is unspecified.

So in the design of rvalue references, this implicit move is not there. If it were, our best way above would not work because the first mention of str would be std::move(str) instead.

Why move return an rvalue reference parameter need to wrap it with std::move()?

In passThroughMove(Widget&& w), w's type is already rvalue reference, std::move(w) just cast it into rvalue reference again.

So std::move(w) returns an rvalue reference, just as w itself.

No, std::move(w) casts to rvalue, while rvalue references are lvalues.

Both functions passThroughMove and passThrough return by value.
They differ, however, in the way they create internally such return value.
Internally, passThroughMove creates its return value by move. A new Widget object (the return value) is created by moving into it, that's the effect of std::move on the return value. passThrough on the other hand creates its own return value by copy.

The fact that the assignment

Widget wt2 = passThrough(std::move(w2));

is done from an rvalue does not change the fact that passThrough is forced to create its return value by copy.

In the output of the code you see the effect of the above semantics plus RVO. Without RVO both assignments should result into two additional move-constructions, which are optimized away.



Related Topics



Leave a reply



Submit