Reputation:
My understanding is incorrect. I thought the point of move is to force T&& instead of a T or T& overload. The signature is
template typename remove_reference::type&& move (T&& arg) noexcept;
I also thought T&& is the same thing as T& except signifies the reference is going out of scope. From my understanding the code in the image should be safe.
rv.append would either create a new copy (which will go out of scope) or reuse rv which is going out of scope. I thought the implementation makes a copy because the move is there. Then it does append again followed by the move. When it returns I thought since its a reference to a temporary it has no idea where the temp came from and should copy it on the stack and postpone its lifetime.
However I am incorrect and the fix is written in there with the image. Why doesn't it work that way? Is T&& being passed by reference like I thought? What else should I know about Rvalue references?
I also don't understand how std::unique_ptr<T> make_unique1(U&& u)
in this example can call two different functions
Upvotes: 0
Views: 146
Reputation: 13526
As you say, T&&
is a reference just like T&
, so returning an rvalue reference to a local variable is the same as returning a regular reference to a local variable, in both cases the local variable (or temporary in this case) will be destroyed when the function exits and you'll get an invalid reference. Lifetime extension does not help because a the lifetime of a temporary bound to the return value of a function is only extended until the function exits.
Returning a string
value fixes this because the return value will be move-constructed (as a new object) from the local/temporary variable you create with move(rv.append(", ").append(ptr))
. That way when the temporary is destroyed the object you returned will be unaffected.
One other comment, your join function should take parameter rv
by value, not by rvalue reference (string rv
instead of string&& rv
). Unless the move constructor is very expensive (which it shouldn't be) the performance will be the same and you will also be able to call your function on temporary values. I can think of very few cases where you would want to declare a variable as an rvalue reference besides in a move constructor (or in template functions, but then it's really a universal reference).
Upvotes: 1
Reputation: 39101
An rvalue reference has a name, therefore it's an lvalue itself. (The idea is if you're able to refer to an object by a name, it's potentially unsafe having it implicitly moved, as it's state is mostly undefined after the move.)
string&& join(string&& rv, const char* ptr) {
// rv is an lvalue here
rv.append(", "); // modifying rv
rv.append(ptr); // modifying again
return move(rv); // move required because rv is an lvalue
}
This returns a rvalue reference to the first argument passed. There's no copy from rv
to the return value of join
, as the return type is a reference and the type of that reference matches the type of the expression move(rv)
.
The return type of join
is an xvalue, which is an glvalue as well as an rvalue. It is therefore bound directly to the const lvalue ref r
(no copies involved).
The temporary returned from meow()
lasts until the end of the full-expression const string& r = join(meow(), "purr");
. As its lifetime isn't extended, it's destroyed and r
becomes a dangling reference.
Upvotes: 1
Reputation: 171263
rv.append would either create a new copy (which will go out of scope) or reuse rv which is going out of scope.
No, it modifies rv
itself, there's no copying. It's a member function call that modifies rv
.
I thought the implementation makes a copy because the move is there.
No, when rv.append()
is called it isn't affected by some other call that happens afterwards to the result, it just calls std::string::append(const char*)
on rv
.
Then it does append again followed by the move. When it returns I thought since its a reference to a temporary it has no idea where the temp came from and should copy it on the stack and postpone its lifetime.
No, std::move
just casts rv
to std::string&&
, there's no magic copying anything onto the stack. The return value is an rvalue-reference bound to rv
, which is an rvalue-reference bound to the temporary returned by meow()
. Then outside the function a const std::string&
is bound to that same temporary, then at the semi-colon the temporary goes out of scope, leaving r
as a dangling reference.
Upvotes: 4