For more exciting content, please pay attention to wechat public number: Back-end technology Cabin

Rvalue semantics in modern C++

Of all the features of modern C++, rvalue semantics (STD ::move and STD ::forward) are probably one of the most magical and difficult. This paper briefly introduces the principle and use of rvalue semantics in modern C++.

1 What is an lvalue, what is an rvalue?

int a = 0;       // a is an lvalue and 0 is an rvalue
int b = rand(a);// b is an lvalue, and rand() is an rvalue
Copy the code

The lvalue is to the left of the equals sign, and the rvalue is to the right

An lvalue has a name and can obtain its memory address according to an lvalue, while an rvalue has no name and cannot obtain an address according to an rvalue.

2 Reference the overlay rule

Lvalue references A& and rvalue references A&& can be superimposed on each other as follows:

A& + A& = A&
A& + A&& = A&
A&& + A& = A&
A&& + A&& = A&&
Copy the code

For example, in the template function void foo(T&& x) :

  • ifTisint&Type,T&&forint&.xIs lvalue semantics
  • ifTisint&&Type,T&&forint&&.xIs rvalue semantics

That is, foo can be passed regardless of whether the input parameter x is lvalue or rvalue. The difference is that in both cases, the compiler derives the type of the template parameter T differently.

3 std::move

3.1 What?

The STD ::move function was introduced in C++11 to implement movement semantics. It is used to move the contents of a temporary variable (and possibly an lvalue) directly to the assigned lvalue object.

3.2 according to?

Now that we know what STD :: Move does, what benefits does it bring to our brick-moving job? For example:

If class X contains a pointer to a resource, the copy constructor of class X is defined in lvalue semantics as follows:

X::X() 
{
  // Request resource (pointer)
}

X::X(const X& other)
{
  // ...
  // Destroy resources
  // Clone the resource in other
  // ...
}

X::~X() 
{
  // Destroy resources
}
Copy the code

Assume the following application code. TMP is no longer used after it is assigned to a.

X tmp;
/ /... After a series of initializations...
X a = tmp;
Copy the code

In the code above, perform the steps:

  • The default constructor is executed once (the default constructor is TMP)
  • Execute the copy constructor again (copy builds the A object)
  • Execute destructor when exiting scope (destruct TMP and a objects)

From a resource perspective, the above code performs a total of two resource requests and three resource releases.

X a = TMP; Can object A ‘steal’ TMP resources directly for our use without affecting the original function? The answer is yes.

X::X(const X& other)
{
  Swap this and other resources using STD ::swap
}
Copy the code

By ‘stealing’ resources from the object TMP, the overhead of resource requisition and release is reduced. The STD ::swap pointer cost is minimal and negligible.

3.3 How?

Now that we know what STD :: Move is going to do, how exactly does it work?

template<class T> 
typename remove_reference<T>::type&&
std::move(T&& a) noexcept
{
  typedef typename remove_reference<T>::type&& RvalRef;
  return static_cast<RvalRef>(a);
}
Copy the code

Remove_reference removes the reference attribute of the input parameter regardless of whether it is an lvalue or an rvalue. RvalRef is an rvalue type and the final return type is an rvalue reference.

3.4 Example

In practice, a temporary variable is passed as an input parameter to STD :: Move and the return value is passed to a function that accepts an rvalue type to ‘steal’ resources from the temporary variable. Note that once a temporary variable is’ stolen ‘, it cannot be read or written to, otherwise undefined behavior will occur.

#include <utility>             
#include <iostream>            
#include <string>              
#include <vector>              
                               
void foo(const std::string& n) 
{                              
  std::cout << "lvalue" << std::endl;
}                              
                               
void foo(std::string&& n)      
{                              
  std::cout << "rvalue" << std::endl;
}                              
                               
void bar(a)                     
{                              
  foo("hello");                // rvalue
  std::string a = "world";      
  foo(a);                      // lvalue
  foo(std::move(a));           // rvalue
}

int main(a)
{
  std::vector<std::string> a = {"hello"."world"};
  std::vector<std::string> b;

  b.push_back("hello");         // Overhead: string copy construct
  b.push_back(std::move(a[1])); // Overhead: string move construct (steal pointer from temporary variable a[1])

  std::cout << "bsize: " << b.size() << std::endl;
  for (std::string& x: b)
    std::cout << x << std::endl;
  bar(a);return 0;
}
Copy the code

4 std::forward

4.1 What?

STD :: Forward is used for perfect forwarding. So what is a perfect retweet? Perfect forwarding realizes the function of keeping the value attribute of a parameter during transmission, that is, if it is an lvalue, it will still be an lvalue after transmission, if it is an rvalue, it will still be an rvalue after transmission.

In simple terms, STD :: Move is used to strongly convert an lvalue or rvalue object to an rvalue semantics, while STD :: Forward is used to preserve both the lvalue semantics of an lvalue object and the rvalue semantics of an rvalue object.

4.2 according to?

#include <utility>
#include <iostream>

void bar(const int& x)
{
  std::cout << "lvalue" << std::endl;
}

void bar(int&& x)
{
  std::cout << "rvalue" << std::endl;
}

template <typename T>
void foo(T&& x)
{
  bar(x);
}

int main(a)
{
  int x = 10; 
  foo(x);  // Output: lvalue
  foo(10); // Output: lvalue
  return 0;
}
Copy the code

Foo (x) and foo(10) both output lValue. Foo (x) outputs lvalue because x is an lvalue, but 10 is an rvalue. Why does foo(10) also output lvalue?

This is because 10 is only an rvalue argument to the function foo, but inside foo, 10 is substituted into the parameter x, which is a named variable, an rvalue, so bar(x) in foo still outputs lvalue.

So the question is, what if we want to preserve the rvalue semantics of x inside foo? STD :: Forward comes in handy.

Simply rewrite the foo function:

template <typename T>
void foo(T&& x)
{
  bar(std::forward<T>(x));
}
Copy the code

4.3 How?

STD :: Forward sounds a bit magical, so how exactly does it work?

template<typename T, typename Arg> 
shared_ptr<T> factory(Arg&& arg)
{ 
  return shared_ptr<T>(new T(std::forward<Arg>(arg)));
}
template<class S>
S&& forward(typename remove_reference<S>::type& a) noexcept
{
  return static_cast<S&&>(a);
}

X x;
factory<A>(x);
Copy the code

If the input argument to factory is an lvalue, then Arg = X&, STD ::forward

= X& according to the overlay rule. Therefore, in this case, STD :: Forward

(Arg) is still an lvalue.

Conversely, if the factory input argument is an rvalue, then Arg = X, STD ::forward

= X. In this case, STD :: Forward

(Arg) is an rvalue.

Just enough to preserve the semantics of lvalue or rvalue!

4.4 Example

Go straight to the code. If the previous understand, I believe that the output of this code can also guess a pretty close.

#include <utility>
#include <iostream>

void overloaded(const int& x)
{
  std::cout << "[lvalue]" << std::endl;
}

void overloaded(int&& x)
{
  std::cout << "[rvalue]" << std::endl;
}

template <class T> void fn(T&& x)
{
  overloaded(x);
  overloaded(std::forward<T>(x));
}

int main(a)
{
  int i = 10; 
  overloaded(std::forward<int>(i));
  overloaded(std::forward<int&>(i));
  overloaded(std::forward<int&&>(i));
  
  fn(i);
  fn(std::move(i));

  return 0;
}
Copy the code

Recommended reading

  • STL source code analysis — Vector
  • STL source code analysis – Hashtable
  • STL source code analysis –algorithm
  • Principles of the ZooKeeper Client
  • Redis implements distributed locking
  • Recommend a few useful efficiency devices
  • Restrict the C/C++ keyword

For more exciting content, please scan the code to follow the wechat public number: back-end technology cabin. If you think this article is helpful to you, please share, forward, read more.