Re: Shooting yourself in the foot with rvalue references Organization: Arcor
On 18 Mai, 23:25, Matthias Hofmann wrote:
[...]
I remember that string literals are usually kept in a read-only section of
the program executable, so that modifying them is undefined behaviour. I
thought that integer literals would be handled in a similar way, but I
just
realized that creating a temporary int is much easier for the compiler
than
creating a temporary character array. I thought that modifying an integer
literal through an rvalue reference was undefined behaviour for the same
reason that modifying string literals is, but as I have learned from your
replies and through some research on the internet, I was in error.
A literal is a literal and cannot be modified. The only things you can
modify are non-const objects. This includes temporary objects. And
rvalue expressions don't necessarily refer to objects. For the purpose
of binding a reference to a pure rvalue that does not refer to any
object, a temporary object is created. In that respect there is little
difference between lvalue-references-to-const and rvalue-references.
Basically, as a beginner, you have to keep the following three things
in mind:
(1) As a rule of thumb: If you feel the need to write a function which
returns an rvalue reference, it is _probably_ the wrong thing to do.
Rvalue references are references. Returning references to function-
local objects is still wrong, no matter what kind of reference it is.
Special exceptions: std::move, std::forward.
If you have a function that returns an object by value, you have an rvalue
already. The only case that I can imagine where it could make sense to
have
an rvalue reference as a function's return type is something like this:
std::string GetString()
{ return "Hello!"; }
std::string&& GetStringIndirect()
{ return GetString(); }
This is exactly what I'm talking about. For some reason, beginners
think that returning rvalue references to function-local objects is
okay. It's not. You're creating a dangling reference here. This is
bad. Very bad.
But on the other hand, this probably prevents return value optimization.
Also, if GetStringIndirect() returned by value rather than by reference,
the
effect would be the same, although maybe one more move construction would
take place. But what about the lifetime of the temporary returned from
GetString()? Does it live as long as the reference it is bound to?
No, it does not.
(2) Keep in mind that the pattern T&& where T is a function template's
deducible type parameter is a special "catch everything"-pattern. It
will catch lvalues as well. Not because an rvalue references binds to
an lvalue but because of a special template argument deduction rule
and because of reference collapsing. Keep in mind that T will be
deduced to be an lvalue reference in case you use an lvalue expression
as function argument. In that case T&& will also be an lvalue
reference (due to reference collapsing).
This is about perfect forwarding. [...]
Actually it's not too difficult, you only have to remember to use the
"catch
everything" pattern T&& when you declare the functions parameters and call
std::forward<T>() on a parameter when you forward it.
Right. I'm just emphasizing this because you can find descriptions
that say T&& is an rvalue reference. But this is only true if T is not
an lvalue reference. In general, T&& is either an rvalue reference or
an lvalue reference depending on what T is. And there is a funny
deduction rule for T in this special case which may turn T&& into an
lvalue reference. This is a "gotcha" some people already ran into. The
anti pattern looks like this:
template<class T> void blah(T const&);
template<class T> void blah(T &&);
overloading idiom for move semantics + templates =
unexpected results due to accitental moves and mutations
(because function template #2 also catches non-const
Lvalues)
(3) If you're not sure how to use rvalue references, don't bother or
limit yourself to writing move constructors and move assignment
operators for your custom types.
This requires some understanding of rvalue references, especially about
the
pitfalls like named rvalues being lvalues.
[...]
I wouldn't say "pitfall" because the code will either not compile or
just be less efficient. The details of all rvalue reference related
rules are complicated. But I believe it's possible to give a good and
short overview of what the basics are. For example, I watched Scott
Meyers "move semantics" ACCU talk recently and liked it very much. I'm
sure he will come up with good rules of thumb similar to or better
than the three points I mentioned. (The video of this talk is publicly
available).
Except that learning them can be a headache... ;-) Yesterday I learned
another rule, which is that if a return statement contains an lvalue of
class type that has the same type as the functions return type, overload
resolution treats the returned object as an rvalue first. Take the
following code for example:
std::unique_ptr Object::Clone()
{
std::unique_ptr temp = ...;
// return std::move( temp );
return temp;
}
I would have thought that the above code would not compile because temp is
an lvalue and thus could not be bound to the parameter of
std::unique_ptr's
move constructor, but 12.8/31 and 12.8/32 of C++0x define this as legal
C++.
Right. In fact, you should not use std::move here because this would
prohibit NRVO as far as I can tell. And NRVO is even better than a
move construction.
--
SG
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]