value category
- expression
- glvalue
- lvalue
- xvalue
- rvalue
- prvalue
- xvalue
- glvalue
- A glvalue [(generalized lvalue)] is an expression whose evaluation determines the identity of an object, bit-field, or function.
- A prvalue is an expression whose evaluation initializes an object or a bit-field, or computes the value of an operand of an operator, as specified by the context in which it appears.
- An xvalue is a glvalue that denotes an object or bit-field whose resources can be reused (usually because it is near the end of its lifetime).
- An lvalue is a glvalue that is not an xvalue.
- An rvalue is a prvalue or an xvalue.
why use rvalue
Simply put, an rvalue is object without name. And rvalue enables the move semantics.
basic note
- You can overload a function to take an lvalue reference and an rvalue reference.
- The compiler treats a named rvalue reference as an lvalue and an unnamed rvalue reference as an rvalue.
- In order to keep original type, you will need
perfect forwardiing
- In order to keep original type, you will need
- You can cast an lvalue to an rvalue reference. (via
std::move
) - Function templates deduce their template argument types and then use reference collapsing rules.
T&&
is given the nameuniversal reference
. And it does not always resolve to a rvalue reference.
#include <iostream>
#include <utility>
void func(int const&)
{
std::cout << "lvalue" << std::endl;
}
void func(int&&)
{
std::cout << "rvalue" << std::endl;
}
template<typename T>
void wrap(T&& a) {
func(std::forward<T>(a));
func(a);
}
int main() {
int a = 0;
int && b = std::move(a);
// print lvalue, lvalue
wrap(a);
wrap(b);
// print rvalue, lvalue
wrap(0);
wrap(std::move(b));
}
rvalue reference
std::move
is a simple cast. It does not put lvalue in undefined state if the result is not consumed immediately.- Thus, use std::move to generate rvalue value reference adds no value
- However, rvalue reference can be used to extend the lifetime of returned rvalue.
reference collapsing
& & -> &
& && -> &
&& & -> &
&& && -> &&
perfect forwarding
- maintain orignal type when passing to another function.
- if we depends on reference collapsing only, the type could change implicitly. (note 2)
- avoid writting multiple function for different value category
copy elision
- rvalue can be bound to const lvalue reference
- as a result, rvalue can be used to call copy constructor
- in fact, for strong exception guranatee,
nonexcept move
>copy
> regularmove
- the standard allow copy to elided for some cases:
- RVO / NRVO: return temporary object / named object from a function
- etc
- some caveat:
- the code still had to be able to work as if the copy were not elided. Namely, there had to be an accessible copy and/or move constructor.
- copy elision can be applied even if copying/moving the object has side-effects
- C++ 17 gurateed copy elision
- Changes the defition of prvalues. No temporay object is created. i.e. no copy is needed
std::string a() {
return "a pony";
}
std::string b() {
return a();
}
int main() {
auto x = b();
}
// In the C++11 model, return "a pony"; initializes the temporary return object of a(),
// which move-constructs the temporary return object of b(), which move-constructs x.
// All the moves are likely elided by the compiler.
// In the C++17 model, return "a pony"; initializes the result object of a(), which is the result object of b(), which is x.
pass by value or reference
For constructor,
- If we pass by reference, we cannot be sure if the reference is stored in the class.
- If we pass by value, we do not need to care about it storage.
- We can also save the performance by doing two moves. One move on the caller, one move on the callee.
- Otherwise, it will still work with two copies, or one copy + one move.
- We also avoid multiple overload of the function.
- Thus, pass by value is preferred
For functions, we might have a problem with unnecessary dealloc.
- Ex, string has a internal capacity that get re-used on copy
- If we pass by value and move it, the string will always dealloc.
- No dealloc on constructor because there is no variable to dealloc.
- If we pass by const reference, we have the choice of move or copy
- However, most classes do not have this re-use pattern. Thus, both patterns can be helpful.
ref-qualified member functions (C++11)
The implicit object parameter this
will have a corresponding type reference.
template <typename T>
class optional
{
// ...
T& value() &;
T&& value() &&;
T const& value() const&;
};
reference
Rvalue references in Chromium Reference collapsing forwarding