C++_Move_Reference - RicoJia/notes GitHub Wiki
========================================================================
========================================================================
- Overview:
- glvalue (generalized l value), prvalue: pure-rvalue, like literals. Xvalue (将亡值). tutorial
- glvalue = xvalue + lvalue, rvalue = prvalue + xvalue
- Expression Category: rvalue, lvalue. Value Category: r-value reference, etc, they are types.
- expression can be: operatori and operand, constants, literals
- reference is just an alias.
- glvalue (generalized l value), prvalue: pure-rvalue, like literals. Xvalue (将亡值). tutorial
-
Reference are internally (also in binary) pretty much the same as pointer
- If you have declared it only, even with value, that's still NOT definition. No memory is allocated.
-
Reference to Bit Field
struct Foo{ std::uint32_t version:4, IHL:3; }; void f(std::size_t sz); Foo f; f(f.IHL); fwd(f.IHL); //Error: fwd(const int& d) is a reference. A ref to bits cannot be created. So you need to create a copy first.
- fix:
auto i = static_cast<std::uint16_t>(f.IHL);
- fix:
-
reference collapsing: (C++98, 03). There are two types:
- rvalue ref to rvalue -> rvalue: int&& &&
- others, e.g,
A& & -> A& (if A)
(as long as there's lvalue reference, you will get lvalue reference) - Key mechanism:
-
explicit reference to reference is not allowed, but compiler can use it when deducing types
int x = 3; auto& & rx = x; // error
-
explicit reference to reference is not allowed, but compiler can use it when deducing types
- Refence Collapsing happens in 4 places:
- template
- auto
auto&& k = x; // if x could be int&, or int&&
-
typedef or alias (using)
template <typename T> class Widget{ public: typedef T&& Universal_Ref; // T could be int&, or int&& }
-
Basic Concepts
- rvalue value cannot be assigned to an lvalue
void foo(int&& T){++T; } viud bar(const int&& T){++T; } int i = 0; foo(i); //i becomes 1; bar(i); //error: this would bind an rvalue reference to an lvalue
- Scalar is never const rvalue
7; // This is int&&, not const int&&
- no object is of "lvalue" or "rvalue" (i.e,t no value-category), only "rvalue-reference".
- The confusing point is: a named variable itself is always an lvalue. But it can have the type of rvalue-reference.
// example 1 Widget&& var1 = makeWidget(); //makeWidget returns an rvalue to var1, but var 1 itself is lvalue, since it's a named variable! // example 2 template<typename T> class Widget { Widget(Widget&& rhs); // rhs’s type is rvalue reference, ... // but rhs itself is an lvalue };
- so to truly get transform these named variables to rvalue, we need perfect forwarding.
- A rvalue reference can be indistinguishable from an lvalue reference: the diff is rvalue_reference can be bound to objects of rvalue category, and
decltype()
will be different -
int&& rrx = 2
: a temporary object of int is created, rvalue refrrx
is bound to the temp object.rrx
is of lvalue in value category, since it has address.- we can modify the value of the temp object, through rrx = 4;
- The confusing point is: a named variable itself is always an lvalue. But it can have the type of rvalue-reference.
- rvalue value cannot be assigned to an lvalue
-
Xvalue_reference
- both
prvalue, xvalue
can be rvalue-
xvalue
are:std::move(x)
,a[n]
,a.m
. 将亡值 -
prvalue
are function return values.
-
- They are valid objects until
;
. If they're not POD, you can treat them as:struct Foo{}; Foo foo(){ return Foo(); } int main() { Foo f; foo() = f; // use default Foo& Foo(const Foo&), which returns an xvalue in this case. Treat it as a valid object until termination. }
- both
-
Anything type that needs to be deduced, by template T or by auto. The foundation of universal reference, is type deduction in a reference-collapsing context.
template <typename T> void f(T&& param); // Even const will make T&& an rvalue! template <typename T> void f(const T&& param);
-
This does not mean any template function T can have T&& as a universal reference. Distinguishing unversal reference and rvalue reference
- R-value refs
// 1. this is rvalue ref void foo(Widget&& param); //2. Also r value - type deduction will not determine universal referenceness template <typename T> void f(std::vector<T>&& param){}
- The standard said: A forwarding reference is an rvalue reference to acv-unqualified template parameter, so
const T&&
is not a forwarding reference. linktemplate <typename T> void const_r_value_ref(const T&& param){ // const T&& param is actually const r-value reference // if param == 1, T is int, decltype(param) is int&& cout<<std::is_rvalue_reference<T>::value<<endl; cout<<std::is_rvalue_reference<decltype(param)>::value<<endl; } // Below won't compile: int i = 1; const_r_value_ref(i);
- So one use is to disallow xvalue and prvalue:
template <class T> void ref (const T&&) = delete;
- So one use is to disallow xvalue and prvalue:
- R-value refs
-
E.g, find the mistake in the above code
#include <utility> void foo(int&&){} int main() { const int&& j = 7; //const int&& is that rvalue? foo(std::forward<int>(j)); return 0; }
- we are passing in const int&& as int&& into foo(int&&), which is not allowed.
-
Misc
-
const char(&a)[5]
is an array of 5 char references
-
-
reference qualifier for member class. This can be combined with function that returns rvalue.
class Foo{ public: Foo() = default; //default constructor won't be generated automatically Foo(Foo&& ){cout<<"Foo move ctor"<<endl; } //move constructor for constructing using a r-value }; class Bar{ public: Bar(){} // Does not go well with Bar().getFoo() Foo& getFoo()& {cout<<"lvalue foo is returned "<<endl; return foo;} //foo is already lvalue // Will return Foo&, but later will trigger r_foo(const Foo&). So not efficient // Also, cannot be overloaded with two getFoo()&& functions // Foo& getFoo()&& { // cout<<"rvalue foo is returned "<<endl; // return foo; // } // good //move is key Foo&& getFoo()&& {cout<<"rvalue foo is returned "<<endl; return std::move(foo);} private: Foo foo; }; void rvalue_ref_func(){ //r_foo is move-constructed auto r_foo = Bar().getFoo(); }
- if not marked with &&, a function can be accessed by both rvalue, or lvalue
========================================================================
========================================================================
-
motivation: to allow the user to call a function, as if directly (which means preserving rvalue reference or lvalue reference)
- Perfect forwarding is actually forwarding the reference, cv qualifiers.
- The rvalue_reference object is an lvalue itself, so we need
std::forward
when passing in rvalue_referenceusing std::unique_ptr<int> = Ptr; void(Ptr&&){ Ptr q = std::make_unique<int>(4); q = p; // Error, need std::forward<int> }
- **Use case: **
template<typename T> void regular_r_ref(T&& param){ cout<<std::is_rvalue_reference<decltype(param)>::value<<endl; } int main(){ // constness int&& j = 8; cout<<std::is_rvalue_reference<decltype(j)>::value<<endl; // is rvalue ref regular_r_ref(std::forward<int>(j)); // is rvalue ref regular_r_ref(j); // NOT rvalue ref }
-
Basics:
- it's different from std::move, as move will simply cast a left value to right value.
-
std::forward
will only cast, if the reference is initialized as rvalue. Therefore, we still need std::move for casting lvalue reference -> rvalue reference
-
Cautions: perfect forwarding cannot be used on everything: Perfect forwarding is built on top of type deduction. If type deduction fails, perfect forwarding fails
-
Braced initializers:
template <typename T> void f(T&& param){ foo(std::forward<T> (param)); } void foo(const std::vector<int>& v){ cout<<"foo, vec"<<endl; } void type_deduction(){ // Error: does not get type-deducted for templated functions. f({1}); }
- But {} gets deduced in auto. So you can use it as a fix
auto il = {1,2,3}; //deduced to std::initializer_list f(il);
- But {} gets deduced in auto. So you can use it as a fix
-
0 or NULL, will be deduced as int, not pointers.
void foo(int* i); f(0); // 0 will be deduced as int. f(nullptr) // nullptr will be deduced as int*
-
Specific to
std::string
-
char 16_t[]
cannot be directly converted to std::string - If
char 16_t[]
is passed in as an argument and perfect-forwarded to std::string, error msg is ugly
-
-
-
Simplified Implementation of
std::forward
//C++ 11 template <typename T> T&& forward (typename std::remove_reference<T>::type& param){ //so param is lvalue reference return static_cast<T&&>(param); // Reference collapsing: if T is int&, you will get int&, if int&&, you'll get int&& } //C++14 template <typename T> T&& forward(std::remove_reference_t<T>& param){ return static_cast<T&&>(param); }
-
Short Summary
- neither std::move or std::forward does anything in runtime.
-
std::move
is casting to r-value reference UNCONDITIONALLY, like (Str&&)str.- Internally, std::move removes the reference and finds the type.
template<typename T> // in namespace std typename remove_reference<T>::type&& // move(T&& param) { using ReturnType = // alias declaration; typename remove_reference<T>::type&&; // get the type of the input return static_cast<ReturnType>(param);}
- T&& can be lvalue reference, as long as T is lvalue reference.
- Internally, std::move removes the reference and finds the type.
- after
std::move
, lvalue cannot be retrieved, but can be assigned to and be used. - Another form:
std::move( InputIt first, InputIt last, OutputIt d_first )
- For
std::vector
, you can dovec2 = std::move(vec1)
- But this works for general
input, output itr
std::move(queue_.begin(), queue_.end(), std::back_inserter(vec))
- For
-
Motivation: When you want to construct an object from an r-value object, we need to allocate memory to the temp object, then copy it over.
- we now want to directly "move" the r-value object into your object. This will be a performance boost.
- The way we do that is by "stealing" the rvalue object address. Using Move Constructor. At the lowest level, move_constructor swaps addresses
#include<utility> class Entity{ public: Entity (Entity&& other){ if(&other != *this){ ... } }
-
std::move will respect constness, so if a const rvalue is passed in, you get const rvalue reference after.
-
Move Constructor does create r-value ref for const, because we might change the rvalue afterwards. So std::move doesn't gurantee if move_constructor will be called afterwards.
const std::string str1 = "lol"; std::string str2 = "hah"; std::string s3(std::move(str1)); //calls the copy constructor cuz str1 is const std::string s4(std::move(str2)); //calls move contructor of string.
-
Move Constructor does create r-value ref for const, because we might change the rvalue afterwards. So std::move doesn't gurantee if move_constructor will be called afterwards.
-
after std::move, the lvalue cannot be assigned again. So use this if you're sure that you won't need this lvalue anywhere else
-
move might not be present, or it's not cheap, or the compiler chooses not to without
noexcept
or some lvalues cannot be moved:- First, not every class has move ctor
-
std::vector::push_back requires
noexcept
on move ctor. Otherwise, copy ctor is used. - move is cheap (constant time-complexity) because:
-
In most containers, the content of the container is stored on heap, and the container stores a pointer to it.
- Move in most containers is just transferring pointer ownership, then set the original ptr to null
-
std::array
is not cheap. Becausestd::array
is a C array with STL interface.- So std::array stores elements individually, there's not a single ptr.
- Std::array will spend linear time "moving", same as copy
- std::string under 15 chars will spend linear time moving, too.
- Because std::string has "small string optmization" (SSO)
- MOtivation is storing a small string on stack instead of on heap usuallty has a performance boost
- However, "moving" a small string is linear time as copying.
-
Motivation: only return a reference when the object's lifetime is longer than the function. Builds the object directly in memory for return.
- both are copy elision (meaning ignoring) part of "copy-elision". (An object is created and used directly, instead of being copied to where it's used.)
- native copy constructor vs copy ctro invoked by =.
-
return value optimization (returning the local object from function directly)
Foo makeFoo(){ Foo f; return f; // RVO takes place! //return std::move(f); }
- both are copy elision (meaning ignoring) part of "copy-elision". (An object is created and used directly, instead of being copied to where it's used.)
-
RVO is
int Foo(){ return 1; }
-
NRVO (Named Return Value Optimization): a named object is returned but not copied
Bar foo(){ Bar b; return b; }
-
When NRVO doesn't happen:
- Deciding which object to return during runtime
Bar foo(){ Bar a, b; if (runtime_condition){ return a; } else{ return b; } }
- Return a global variable, or input Parameter
Bar global_var; Bar foo_global(){return global_var; } Bar foo_param(Bar b){return b; }
- Return by std::move()
Bar foo(){ Bar b; return std::move(b); // Disables NRVO }
- Assignment: using
operator =
with an existing object uses copy/move ctors, no RVO!Bar foo(){ return Bar(); } int main(){ Bar s; s = foo(); // s already exists, no RVO performed }
- Return a member
struct Bar{ Widget w; } Widget woo(){return Bar().w; }
- Deciding which object to return during runtime
-
std::ref
- Creates a
reference_wrapper
object that acts like a reference. - Uses
- in std::thread, cuz it copies arguments into func
cpp int i = 3; void func(int&); std::thread(func, std::ref(i));
- in a container.
cpp int& arr[] = {x,y,z}; //illegal //use reference wrapper instead reference_wrapper<int> arr[] {x,y,z};
- in std::thread, cuz it copies arguments into func
- Creates a
-
std::reference_wrapper
makes a struct copyable, assignable references. You can directly use it as a reference.- **Use Case: ** 可以用 predicate 和 reference wrap 来达到简化memory use 和code length:
#include <functional> std::vector<std::reference_wrapper<type>> vec(type_vec.begin(), type_vec.end()); vec.front().get().data_member //when you use member accessor on a reference wrapper, it s operating on the wrapper, not the object being wrapped. So you need get().
- since c++ 17, the iterator is the same as S&. otherwise, you have to manually unwrap the reference wrapper
- **Use Case: ** 可以用 predicate 和 reference wrap 来达到简化memory use 和code length:
-
std::ref
vsstd::reference_wrapper
-
std::reference_wrapper
wraps around a ptr, copyable and movable. Not working with rvalue references. -
std::ref
returns anstd::reference_wrapper
object -
template <typename T>
will usually be by-value, except this casefoo<int&>(f);
-