C++11 -- right value reference

Keywords: C++ C++11

catalogue

preface

1, Concept of right value reference

        1.1 concept of left value and right value

        1.2 comparison between reference and right value reference

2, Role of R-value reference

        2.1 referenced defects

        2.1 mobile semantics

         2.2 specific application of right value reference

        2.3 comparison and quotation summary

3, Right value refers to left value (move)

4, Perfect transformation

preface

        There is also a reference in C++98, which is an lvalue reference, but lvalue reference has some shortcomings. The right value reference of C++11 makes up for the deficiency of left value reference.

        Both lvalue and rvalue references are aliases.

        Note: the following references represent the lvalue references of C++98.

1, Concept of right value reference

        An R-value reference is also an alias for a space. Only right values can be referenced. Use is to add two & & after the type.

#include<iostream>
using namespace std;

int Add(int x, int y){
	return x + y;
}

int main(){
	const int&& ra = 10;//rvalue reference 
	//The return value of the function is a temporary variable, which is an R-value
	int&& ret = Add(2, 3);//rvalue reference 
	return 0;
}

        1.1 concept of left value and right value

        Left value and right value are the concepts of C language, but C language does not give strict standards. It is generally believed that the left value that can be placed on the left of the equal sign "=" is the left value, and the right value that can be placed on the right of the equal sign is the right value. But this statement is wrong.

For example:

#include<iostream>
using namespace std;

int main(){
	//a. B is the left value and 10 and 20 are the right value
	int a = 10;
	int b = 20;
	//b can also be placed to the right of the equal sign
	//a can also be placed to the right of the equal sign
	a = b;
	b = a;

	return 0;
}

It is difficult to distinguish between left value and right value. Here we generally think:

  • Lvalue: it is generally a value that can be modified. It can take an address. It is usually a variable.
  • R-value: it is generally a constant (except const modification), and the value transfer and return of an expression or function (generating a temporary variable).

         Note: the returned value is an R-value.

C++11 makes a strict distinction between right values:

  • Pure right value: for example, constant, expression value a+b
  • Dead value: for example, the function passes a value and returns the intermediate result of the expression. As the name suggests, the space that will lose value will be released soon.

        1.2 comparison between reference and right value reference

  • Reference, you can only reference the left value, not the right value. However, const references can refer to both left and right values.
int main(){
    //a is the left value and 10 is the right value
	int a = 10;
	int& ra1 = a;
	const int& ra2 = a;

	//int& ra3 = 10;// Compilation error, 10 is the right value
	const int& ra3 = 10;

	return 0;
}
  • Right value reference: you can only reference right values, not left values. However, the right value reference can refer to the left value after move. Move is introduced later. It can be considered that it changes the attribute of the left value to the right value.
int main(){
	//a is the left value and 10 is the right value
	int a = 10;
	int&& ra1 = 10;

	//int&& ra2 = a;// A is an lvalue, compilation error
	int&& ra2 = move(a);//Just move

	return 0;
}

2, Role of R-value reference

        Right value references and references are aliases. Why do you propose right value references?

        2.1 referenced defects

#include<iostream>
using namespace std;

class String{
private:
	char *_str;
public:
    //structure
	String(char *str = " "){
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	//copy construction 
	String(const String& s){

		cout << "String(const String& s)-copy construction " << endl;
		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
	}

	
};

String Fun(String& s){
	String ret(s);
	return ret;
}
~String()
{
    if(_str)
        delete[] _str;
}
int main(){

	String s1("Lvalue");
	String s2 = Fun(s1);

	getchar();
	return 0;
}

        We know that references as parameters and return values can reduce copy construction, especially for deep copy, which can improve efficiency. However, when the return value is a local object of a function, it cannot be returned by reference and needs to be returned by passing a value.

//Need to pass value back
String Fun(String& s){
	String ret(s);
	return ret;
}

        When the return value function return value is assigned to another object s2. The copy constructor of String class will be called for deep copy.

String s1("Lvalue");
String s2 = Fun(s1);

      When ret returns according to the value, it must copy and construct a temporary object, and deep copy is required. When assigning the return value of the Fun function to s2, that is, copying the temporary object to construct s2 also requires deep copying. Careful observation shows that the contents of s2 and temporary object space are the same, and they all have independent spaces, which is equivalent to creating three identical objects. This is a waste of space and program efficiency will be reduced.

        Let's talk about how to optimize.

        2.1 mobile semantics

  • Move semantics: move resources in one object to another object.

The above defects can be optimized as follows:

        We know that the content of the temporary variable is the same as that of s2, and the temporary object is a dead value. We can copy the temporary object to construct s2 without deep copy, but replace the resources in the temporary object space with object s2.

        The temporary object is a dead value, that is, an R-value. We can overload a copy constructor whose parameter is an R-value reference. We call this copy constructor the move constructor.

         The right value of parameter s refers to a temporary variable. Move the resource of the temporary variable to the this pointer, that is, s2. When the temporary object is constructed s2, it will be destroyed.

//Mobile structure
String(String&& s){
    _str = s._str
	s._str = nullptr;
}

The whole process:

        Since ret is an lvalue, when copying and constructing a temporary object, the copy constructor is called, while the temporary object is an lvalue (dead value). When constructing s2, the move construct will be called to replace the resources of the temporary object with s2. This reduces the process of one deep copy.

#include<iostream>

using namespace std;

class String{
private:
	char *_str;
public:
	String(char *str = " "){
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	//copy construction 
	String(const String& s){

		cout << "String(const String& s)-copy construction " << endl;
		_str = new char(strlen(s._str) + 1);
		strcpy(_str, s._str);
	}
	//Mobile structure
	String(String&& s){
		cout << "String(const String& s)-Mobile structure" << endl;
		_str = s._str;
		s._str = nullptr;
	}

	
	~String()
	{
		if (_str)
			delete[] _str;
	}
	
};

//Need to pass value back
String Fun(String& s){
	String ret(s);//Call copy construct
	return ret;
}

int main(){

	String s1("Lvalue");
	String s2(Fun(s1));//Call move construct

	return 0;
}

  Here, the compiler is optimized, and the process of ret calling the copy constructor to construct temporary objects is omitted.

be careful:

  • The parameters of the move constructor must not be set to the R-value reference of const type, otherwise it cannot be modified and the resources cannot be transferred.
  • In C++11, the compiler will generate a mobile structure for the class by default. The mobile structure is a shallow copy. Therefore, when resource management is involved in the class, you must display your own mobile structure.

         2.2 specific application of right value reference

         The main application of right value reference is to overload the mobile constructor and use the dead value to exchange the spatial content of the dead value into the object to be copied. Reduced deep copy.

  • The right value refers to the parameter of the function

        Since the right value reference refers to the right value (will be dead value), when the parameter needs to be copied and constructed in the function body, the mobile copy construction will be called. Reduce deep copies. increase of efficiency.

  • The function returns by passing a value and receives it with an object.

        The function passes a value and returns a temporary object, which is a dead value. Then the object is received and the temporary object is copied to construct the object. The move copy constructor will be called to reduce the number of deep copies.

        2.3 comparison and quotation summary

         The essence of reference and R-value reference is to reduce copy. The right value reference makes up for the deficiency of reference. Right value reference improves the efficiency of value passing and return.

quote:

        References as parameters and return values can reduce copy construction. However, when the returned object is out of the scope, it is no longer in the scope and can only be returned by value.

        If there is no right value reference and you receive it with an object, the copy structure will be called. For containers such as string/vector, you need to make a deep copy. Low efficiency.

Right value reference:

        The main application of right value reference is to overload the mobile constructor and use the dead value to exchange the spatial content of the dead value into the object to be copied. Reduced deep copy.

        When the function passes a value and returns, the object is used to receive it, and the mobile constructor will be called. For containers such as string/vector, there is no need to make a deep copy, just exchange the right value reference into the constructed object

        The right value reference is used as a parameter. If the right value reference needs to be copied in the function body, the mobile structure will be called instead of deep copy.

3, Right value refers to left value (move)

        According to the syntax, an R-value reference can only refer to an R-value. However, in some scenarios, the right value needs to be used to reference the left value to realize the mobile semantics.

        When the right value refers to an lvalue, the lvalue needs to be converted to an right value through the move function. It can be understood as changing an lvalue attribute to an lvalue return.

int main(){
	//a is the left value and 10 is the right value
	int a = 10;
	int&& ra1 = 10;

	int&& ra2 = move(a);//Just move

	return 0;
}

         Note: the life cycle of the transformed lvalue does not change with the transformation of lvalue. Move will not destroy lvalue. However, after move, the content of the lvalue will be changed. If the lvalue will be used later, use move with caution.

        There is also a move function in STL, which moves elements in a range to another location.

As follows: use the above class.

4, Perfect transformation

        Forwarding is: in accordance with the type of template parameters, the parameters are passed to another function called in the function template.

#include<iostream>
using namespace std;

void Fun(int &x){ 
	cout << "lvalue ref" << endl; 
}
void Fun(int &&x){ 
	cout << "rvalue ref" << endl;
}

template<typename T>
void PerfectForward(T &&t){
	Fun(t); 
}

int main()
{
	PerfectForward(10); //10 is the right value

	return 0;
}

         The following PerfectForward() is the converted template function, and Func is the actual objective function. But here's a problem. As follows: 10 is an R-value, but it does call a function referenced by an l-value. Description during the forwarding of PerfectForward() template function, the right value attribute of 10 is lost.

          Perfect forwarding: the objective function always hopes that the actual type of the parameter will not change due to the conversion function. It's like the conversion function doesn't exist. That is, when a function template passes its own formal parameters to other functions, if the corresponding argument is an lvalue, it converts to an lvalue. If the corresponding argument is an lvalue, it converts to an lvalue.

        The perfect transformation needs to be realized through the forward function.

void Fun(int &x){ 
	cout << "lvalue ref" << endl; 
}
void Fun(int &&x){ 
	cout << "rvalue ref" << endl;
}

template<typename T>
void PerfectForward(T &&t){
	Fun(forward<T>(t)); //Call the forward function on the target function parameter of the conversion function
}
int main()
{
	PerfectForward(10); // rvalue ref

	system("pause");
	return 0;
}

Posted by yeldarb on Mon, 25 Oct 2021 18:17:03 -0700