c++11 left value reference, right value reference

Reprint https://zhuanlan.zhihu.com/p/97128024

Supplementary reference

http://www.manongjc.com/article/39008.html

lvalue reference

Let's take a look at the traditional left reference.

int a = 10;
int &b = a;  // Define an lvalue reference variable
b = 20;      // Modify the value of reference memory by left value reference

The lvalue reference is actually the same as the ordinary pointer at the assembly level; the definition of reference variable must be initialized, because the reference is actually an alias, and the compiler needs to be told whose reference is defined.

int &var = 10;

The above code can't be compiled, because 10 can't get the address, and can't get the address for an immediate data, because the immediate data is not stored in memory, but in a register, which can be solved by the following methods:

const int &var = 10;

The constant number 10 is used to reference the constant number 10, because a temporary variable is generated in the memory to save 10. This temporary variable can be used to get the address. Therefore, var refers to this temporary variable, which is equivalent to the following operations:

const int temp = 10; 
const int &var = temp;

Based on the above analysis, the following conclusions are drawn:

  • Left value reference requires that the value on the right must be able to get the address. If it cannot get the address, it can use constant reference;
    But after using the constant reference, we can only read the data through the reference, and cannot modify the data, because it is modified by const as a constant reference.

Then C++11 introduces the concept of right value reference, which can solve this problem well.

rvalue reference

C + + has no standard definition for left and right values, but there is a widely accepted saying:

  • Address can be taken, with a name, non temporary is the left value;
  • If you can't get an address, if you don't have a name, the temporary value is the right value;

It can be seen that the immediate value and the value returned by the function are all right values, while the non anonymous objects (including variables), references returned by the function and const objects are all left values.

In essence, creation and destruction are controlled by the compiler behind the scenes. Programmers can only ensure that the right value (including the immediate number) is valid in the line of code, while the left value (including the reference of local variable returned by function and const object) is the user created value whose lifetime can be known through scope rules.

Define the format of the right value reference as follows:

Type & & reference name = right value expression;

Right value reference is a new feature of C++ 11, so the reference of C++ 98 is left value reference. The right value reference is used to bind to the right value. After binding to the right value, the lifetime of the right value that would have been destroyed will be extended to the lifetime of the right value reference bound to it.

int &&var = 10;

At the assembly level, right value references do the same thing as regular references, that is, they generate temporary quantities to store constants. However, the only difference is that the right value reference can read and write, while the regular reference can only read.

The existence of right value reference is not to replace left value reference, but to make full use of right value (especially temporary object) construction to reduce object construction and deconstruction operations to improve efficiency.

A simple sequential stack is implemented in C + +:

class Stack
{
public:
    // structure
    Stack(int size = 1000) 
	:msize(size), mtop(0)
    {
	cout << "Stack(int)" << endl;
	mpstack = new int[size];
    }
	
    // Deconstruction
    ~Stack()
    {
	cout << "~Stack()" << endl;
	delete[]mpstack;
	mpstack = nullptr;
    }
	
    // copy construction
    Stack(const Stack &src)
	:msize(src.msize), mtop(src.mtop)
    {
	cout << "Stack(const Stack&)" << endl;
	mpstack = new int[src.msize];
	for (int i = 0; i < mtop; ++i) {
	    mpstack[i] = src.mpstack[i];
	}
    }
	
    // Assignment overloading
    Stack& operator=(const Stack &src)
    {
	cout << "operator=" << endl;
	if (this == &src)
     	    return *this;

	delete[]mpstack;

	msize = src.msize;
	mtop = src.mtop;
	mpstack = new int[src.msize];
	for (int i = 0; i < mtop; ++i) {
	    mpstack[i] = src.mpstack[i];
	}
	return *this;
    }

    int getSize() 
    {
	return msize;
    }
private:
    int *mpstack;
    int mtop;
    int msize;
};

Stack GetStack(Stack &stack)
{
    Stack tmp(stack.getSize());
    return tmp;
}

int main()
{
    Stack s;
    s = GetStack(s);
    return 0;
}

The operation results are as follows:

Stack(int)             // Construction of s
Stack(int)             // Construction of tmp
Stack(const Stack&)    // tmp copy constructs temporary objects on the main function stack frame
~Stack()               // tmp decomposition
operator=              // Temporary object assigned to s
~Stack()               // Analysis of temporary objects
~Stack()               // s decomposition

In order to solve the problem of shallow copy, we provide a self-defined copy constructor and assignment operator overloaded function for the class, and the internal implementation of these two functions is very time-consuming and resource intensive (first, open up a large space, and then copy the data one by one). Through the above operation results, we find that the copy construction and assignment overload are used in two places, namely, tmp copy construction ma The temporary objects and temporary objects on the in function stack frame are assigned to s, in which tmp and temporary objects are destroyed after their respective operations, which makes the program very inefficient.

So in order to improve efficiency, can we directly give memory resources held by tmp to temporary objects? Can I directly give the resources of temporary objects to s?

In C++11, we can solve the above problems by providing copy constructor with right value reference parameter and overload function of assignment operator

// Copy constructor with right value reference parameter
Stack(Stack &&src)
    :msize(src.msize), mtop(src.mtop)
{
    cout << "Stack(Stack&&)" << endl;

    /*There is no need to re open memory copy data here. Give src resources directly to the current object, and then leave src empty*/
    mpstack = src.mpstack;  
    src.mpstack = nullptr;
}

// Overloaded function of assignment operator with right value reference parameter
Stack& operator=(Stack &&src)
{
    cout << "operator=(Stack&&)" << endl;

    if(this == &src)
        return *this;
	    
    delete[]mpstack;

    msize = src.msize;
    mtop = src.mtop;

    /*There is no need to re open memory copy data here. Give src resources directly to the current object, and then leave src empty*/
    mpstack = src.mpstack;
    src.mpstack = nullptr;

    return *this;
}

The operation results are as follows:

Stack(int)             // Construction of s
Stack(int)             // Construction of tmp
Stack(Stack&&)         // Call the copy constructor with the right value reference to directly give the tmp resource to the temporary object
~Stack()               // tmp decomposition
operator=(Stack&&)     // Call the overloaded function of assignment operator with right value reference to directly give the temporary object resource to s
~Stack()               // Analysis of temporary objects
~Stack()               // s decomposition

The program automatically calls the copy constructor with right value reference and the overload function of assignment operator, which greatly improves the efficiency of the program, because the memory copy data has not been re opened.

mpstack = src.mpstack;  

The reason for direct assignment is that the temporary object is about to be destroyed, and there will be no shallow copy problem. We can assign the resources held by the temporary object to the new object directly.

Therefore, the temporary amount will automatically match the member methods of the right reference version, aiming to improve the efficiency of memory resource utilization.

Copy construction and assignment overloaded function with right value reference parameter, also called move constructor and move assignment function, where move refers to move temporary resources to the current object, and the temporary object does not hold resources, which is nullptr. In fact, there is no data movement, no memory development and data copy.

Published 2 original articles, won praise 3, visited 4306
Private letter follow

Posted on Mon, 09 Mar 2020 20:26:28 -0700 by Benny007