C++11 std::move std::forward left and right value references moving constructors

  • 2020-11-20 06:10:58
  • OfStack

About C++11 new features of std::move, std::forward, left and right values reference there are a lot of online information, I mainly for the test performance of 1 test, to sort out the logic 1, first of all, the left value is more familiar, right value is a temporary variable, meaning that once used will not be used again. For these two types of values, l_value references and r_value references are introduced, as well as the concept of reference folding.

1. Sample test of rvalue reference


#include <iostream>
using namespace std;
​
// create 1 A test class 
class A
{
public:
  A() : m_a(55)
  {
  }
​
  int m_a;
};
​
void funcA(A&& param) //  Rvalues refer to parameters and accept only rvalues 
{
  cout << param.m_a << endl; // param with a The address of the 1 Send, just take 1 A new name 
}
​
int main()
{
  A a;
  funcA(move(a)); // It must be converted to an rvalue 
  cout << a.m_a << endl; // Print normally, so std::move It doesn't have the ability to move 
  return 0;
}

2. Test examples of l_value and r_value references, and elicit universal references

Construct a set of overloaded functions that take rvalues, lvalues, and const A & Parameter overload function.


void funcA(const A& param)// Both rvalue references can be accepted , An lvalue reference can also be accepted , But there are 1 An implicit conversion const A&
void funcA(A& param)//  Accept an lvalue reference  
void funcA(A&& param) //  Accept an rvalue reference 

const A & param can accept both r_value and l_value references, but there is an implicit conversion and const usage is limited.


#include <iostream>
using namespace std;
​
// create 1 A test class 
class A
{
public:
  A() : m_a(55) //  The constructor 
  {
    cout << "Constructor" << endl;
  }
  A(const A & other) : m_a(55) // copy The constructor 
  {
    cout << "Copy Constructor" << endl;
    if (this == &other)
    {
      return;
    }
    this->m_a = other.m_a;
  }
  A& operator=(const A& other) //  Assignment constructor 
  {
    cout << "= Constructor" << endl;
    if (this == &other)
    {
      return *this;
    }
    this->m_a = other.m_a;
    return *this;
  }
  int m_a;
};
void test(A&& pa) // Test for rvalue 
{
  cout << " Only rvalues are accepted " << endl;
}
void funcA(const A& param) //  Both rvalue references can be accepted , An lvalue reference can also be accepted , But there are 1 An implicit conversion const A&
{
  //test(param); // Compile, however, param Rvalue is acceptable, but param Is converted to const The left value 
  //test(std::forward<A>(param)); // Compile, however, param Rvalue is acceptable, but param Is converted to const The left value 
  cout << param.m_a << endl; 
}
void funcA(A& param) //  Accept an lvalue reference  
{
  //test(param); // Compile, however, param Rvalue is acceptable, but param Is converted to the left value 
  test(std::forward<A>(param)); // Compile pass, pass forward forwarding 
  cout << param.m_a << endl;
}
void funcA(A&& param) //  Accept an rvalue reference 
{
  //test(param); // Compile, however, param Is converted to the left value 
  test(std::forward<A>(param)); // Compile pass, pass forward forwarding 
  cout << param.m_a << endl;
}
​
int main()
{
  A a;
  const A& b = a;
  funcA(a);
  funcA(move(a));
  funcA(b);
  cout << a.m_a << endl; // Print normally, so std::move It doesn't have the ability to move 
  return 0;
}

For this C++11 introduces the concept of universal references, making it possible to accept both r_value and l_value references without so many overloaded functions. But inside the function, we need to call one more left-valued or right-valued function, we need the forward template class.


#include <iostream>
using namespace std;
​
// create 1 A test class 
class A
{
public:
  A() : m_a(new int(55)) //  The constructor 
  {
    cout << "Constructor" << endl;
  }
  A(const A & other) : m_a(new int(55)) // copy The constructor 
  {
    cout << "Copy Constructor" << endl;
    if (this == &other)
      return;
    this->m_a = other.m_a;
  }
  A& operator=(const A& other) //  Assignment constructor 
  {
    cout << "= Constructor" << endl;
    if (this == &other)
      return *this;
​
    this->m_a = other.m_a;
    return *this;
  }
  int* m_a;
};
void test(A&& pa) // Test for rvalue 
{
  cout << " Only rvalues are accepted " << endl;
}
void test(A& pa) // Tests for an lvalue 
{
  cout << " Only accept left values " << endl;
}
​
template<class T>
void funcA(T&& param)
{
  test(std::forward<T>(param)); // Compile pass, pass forward Perfect forward 
  cout << *param.m_a << endl;
}
​
int main()
{
  A a;
  funcA(a);
  funcA(move(a));
  cout << *a.m_a << endl; // Print normally, so std::move It doesn't have the ability to move 
  return 0;
}

3. Derivation of the move constructor

All of the above features are the use of temporary variables, as much as possible to use temporary variables generated in the middle, to improve performance, so-called squeeze the final performance. Two things to note about the move constructor

1. The argument (to be moved) must be rvalue when the move constructor is called.

2. The movee can no longer be used after the move constructor is called.


#include <iostream>
using namespace std;
​
// create 1 A test class 
class A
{
public:
  A() : m_a(new int(55)) //  The constructor 
  {
    cout << "Constructor" << endl;
  }
  A(const A & other) : m_a(new int(55)) // copy The constructor 
  {
    cout << "Copy Constructor" << endl;
    if (this == &other)
    {
      return;
    }
    this->m_a = other.m_a;
  }
  A& operator=(const A& other) //  Assignment constructor 
  {
    cout << "= Constructor" << endl;
    if (this == &other)
    {
      return *this;
    }
    this->m_a = other.m_a;
    return *this;
  }
​
  A(A&& other) : m_a(other.m_a) //  Move constructor, the argument is 1 A right value, 
  {
    cout << "Move Constructor" << endl;
    if (this == &other)
    {
      return;
    }
    other.m_a = nullptr; // Clears the moved object data after it has been moved 
  }
​
  int* m_a;
};
void test(A&& pa) // Test for rvalue 
{
  cout << " Only rvalues are accepted " << endl;
}
void test(A& pa) // Tests for an lvalue 
{
  cout << " Only accept left values " << endl;
}
​
template<class T>
void funcA(T&& param)
{
  test(std::forward<T>(param)); // Compile pass, pass forward Perfect forward 
  cout << *param.m_a << endl;
}
​
int main()
{
  A a;
  funcA(a);
  funcA(move(a));
  A b(move(a)); // Call the move constructor , The new object is b object 
  cout << *a.m_a << endl; // Data has been moved and the program crashed 
  return 0;
}

Move constructor 1 reduces temporary memory requests to a certain extent, reduces unnecessary copies, and saves space and time. The above features still need to be noted in the use of many places, if I encounter it will be added here in time, share with you, 1 start.


Related articles: