1
votes

If I have no use for a variable after I pass it to a function, does it matter whether I pass it a non-const lvalue reference or use std::move to pass it an rvalue reference. The assumption is that there are two different overloads. The only difference in the two cases is the lifetime of the passed object, which ends earlier if I pass by rvalue reference. Are there other factors to consider?

If I have a function foo overloaded like:

void foo(X& x);
void foo(X&& x);

X x;
foo(std::move(x));   // Does it matter if I called foo(x) instead?
// ... no further accesses to x
// end-of-scope
2

2 Answers

1
votes

The lifetime of an object does not end when it is passed by rvalue reference. The rvalue reference merely gives foo permission to take ownership of its argument and potentially change its value to nonsense. This might involve deallocating its members, which is a kind of end of lifetime, but the argument itself lives to the end of the scope of its declaration.

Using std::move on the last access is idiomatic. There is no potential downside. Presumably if there are two overloads, the rvalue reference one has the same semantics but higher efficiency. Of course, they could do completely different things, just for the sake of insane sadism.

1
votes

It depends on what you do in foo():

  • Inside foo(), if you store the argument in some internal storage, then yes it does matter, from readability point of view, because it is explicit at the call site that this particular argument is being moved and it should not be used here at call site, after the function call returns.

  • If you simply read/write its value, then it doesn't matter. Note that even if you pass by T&, the argument can still be moved to some internal storage, but that is less preferred approach — in fact it should be considered a dangerous approach.

Also note that std::move does NOT actually move the object. It simply makes the object moveable. An object is moved if it invokes the move-constructor or move-assignment:

void f(X && x) { return; }
void g(X x) { return; }

X x1,x2;
f(std::move(x1)); //x1 is NOT actually moved (no move constructor invocation).
g(std::move(x2)); //x2 is actually moved (by the move-constructor).

//here it is safe to use x1
//here it is unsafe to use x2

Alright it is more complex than this. Consider another example:

void f(X && x) { vec_storage.push_back(std::move(x)); return; }
void g(X x) { return; }

X x1,x2;
f(std::move(x1)); //x1 is actually moved (move-constructor invocation in push_back)
g(std::move(x2)); //x2 is actually moved (move-constructor invocation when passing argument by copy).

//here it is unsafe to use x1 and x2 both.

Hope that helps.