It's safe? Rvalues ββand reference return
#include <iostream>
#include <string>
using namespace std;
class Wrapper
{
public:
std::string&& get() &&
{
std::cout << "rvalue" << std::endl;
return std::move(str);
}
const std::string& get() const &
{
std::cout << "lvalue" << std::endl;
return str;
}
private:
std::string str;
};
std::string foo()
{
Wrapper wrap;
return wrap.get();
}
std::string bar()
{
Wrapper wrap;
return std::move(wrap.get());
}
std::string fooRvalue()
{
return Wrapper().get();
}
std::string barRvalue()
{
return std::move(Wrapper().get());
}
int main() {
while(1)
{
std::string s = foo();
std::string s2 = bar();
std::string s3 = fooRvalue();
std::string s4 = barRvalue();
}
return 0;
}
Is the return value const std :: string &, and std :: string && & safe in this use case (pretend std :: string is another type that cannot be copied). I thought it wouldn't work because the refernce is supposed to point to some local variable that is out of scope, but it seems to work fine? Also I've seen the && syntax used before, did I use it correctly (I've limited it when the Wrapper itself is an rvalue).
I'm just a little confused when I return a link, it's safe, I thought the rule was that the object should have survived the return value, but I've seen things like the standard library and have previously validated a restore link. If I store the reference they give me in a variable (it's not a reference), everything seems to work fine if I return that stored variable. Can someone correct me and give me some good rules to follow.
Here's the ideon I was playing with, it all works: http://ideone.com/GyWQF6
source to share
Yes, this is the correct and safe way to implement a getter member. But you can make one improvement, which I will come to. The key is that the lifetime of a member is std::string
(almost) the same as the lifetime of the object Wrapper
that contains it. And the temporary is destroyed only at the end of the full expression, where it appears, and not immediately after its "use". Thus, in return std::move(Wrapper().get());
is created Wrapper
, invoked get()
, invoked std::move
, the return value is returned, and only then is Wrapper
it string
destroyed.
The only danger is that someone is linking another link to your member. But that would be their mistake, and regular constant reference getters have the same danger. This is equivalent to being wrong const char* ptr = "oops"s.c_str();
.
std::string foo()
{
Wrapper wrap;
return wrap.get();
}
foo
copy creates a return value from a member. I hope this is not a surprise.
std::string bar()
{
Wrapper wrap;
return std::move(wrap.get());
}
bar
also copies the return construct from the member. What happens here, since it wrap
is an lvalue, the lvalue is called get()
, which returns the const
lvalue. (Informally " const std::string&
".) Then move
converts that value to x, but it's still const
. (Informally, const std::string&&
".) The move constructor string(string&&)
here cannot be the same as an argument const
. But the copy constructor string(const string&)
can be the same and called.
If you were expecting to bar
move the return construct, you can add a third overload for non-const lvalues:
std::string& get() & { return str; }
(If you don't want to allow the member to change, note that you've already done so. For example, someone might have done it std::move(wrap).get() = "something";
.)
Also, if you had return std::move(wrap).get();
, then it would result in the construction of the return value even without the third overload.
std::string fooRvalue()
{
return Wrapper().get();
}
fooRvalue
move creates a return value because rvalue is used get()
. As already mentioned, it Wrapper
lives long enough for this structure to be safe.
std::string barRvalue()
{
return std::move(Wrapper().get());
}
The barRvalue
challenge is move
completely useless. The expression is Wrapper().get()
already the value of x, so it move
just converts the value of x of type std::string
to ... value of x of type std::string
. However, just as safe.
source to share
The return value object of the function will be initialized before destroying the local variables and temporary files created in the expression return
. Thus, it is perfectly legal to move from locals / temporaries to function return values.
These four global functions work because they return objects, not object references. And this object is instantiated before any of the LANs are destroyed by their constructor reference.
barRvalue
is rather redundant (since the result is Wrapper().get()
already an rvalue reference), but functional.
I'm just a little confused when returning a link safely
A function that returns a reference to a destroyed object is unsafe. Returning a reference to a local object or part of a local object is unsafe.
Returning a reference to *this
or some part is *this
safe because the object this
will go into a function that returns a reference. This is no different than returning a reference to a parameter that you accept by pointer / reference. The object will go into a function call that returns a reference.
source to share