- When compiling C + + code, there are sometimes lvalue errors or lvalue errors. What are lvalues and lvalues???
1, Left and right values
-
What are left and right values?
-
Lvalue: there is a value in memory that determines the storage address, has a variable name, and still exists at the end of the expression. In short, lvalue is a non temporary object.
-
Right value: the value that will be destroyed at the end of the expression if there is no storage address and variable name in memory. In short, the right value is a temporary object.
int a = 0; // In this statement, a is the left value and 0 is the temporary value, which is the right value.
-
Lvalues can be divided into two categories: non constant lvalues and constant lvalues;
int a=10; // a is the non constant lvalue (there are definite storage address and variable name) const int a1=10; //a1 is a constant lvalue (there are definite storage addresses and variable names) const int a2=20; //a2 is a constant lvalue (there are definite storage addresses and variable names)
-
Similarly, right values can also be divided into two categories: non constant right values and constant left values.
int a=10; // 10 is the non constant right value const int a1=10; const int a2=20; a1+a2 // (a1+a2) is a constant right value
2, Lvalue reference to lvalue reference
-
Now that you know lvalues and rvalues, what are lvalues and rvalues?
-
Lvalue reference: in fact, it is a reference bound to lvalue. Lvalue reference is obtained through &.
- Example of lvalue reference:
int a=10; //Non constant lvalue (with definite storage address and variable name) const int a1=10; //Constant lvalue (with definite storage address and variable name) const int a2=20; //Constant lvalue (with definite storage address and variable name) //Non constant lvalue reference int &b1=a; //Correct, a is a non constant lvalue, which can be referenced and bound by non constant lvalues int &b2=a1; //Error: a1 is a constant lvalue and cannot be bound by a non constant lvalue reference int &b3=10; //Error, 10 is a non constant right value and cannot be bound by non constant left value reference int &b4=a1+a2; //Error, (a1+a2) is a constant right value and cannot be bound by a non constant left value reference //Constant lvalue reference const int &c1=a; //Correct, a is a non constant lvalue, which can be bound by non constant lvalue reference const int &c2=a1; //Correct, a1 is a constant lvalue and can be bound by a non constant lvalue reference const int &c3=a+a1; //Correct, (a+a1) is a non constant right value, which can be bound by a constant right value reference const int &c4=a1+a2; //Correct, (a1+a2) is a constant right value, which can be bound by non constant right value reference
- Summary: non constant lvalue references can only be bound to non constant lvalues; Constant lvalue reference can be bound to all value types, such as non constant lvalue, constant lvalue, non constant right value, constant right value, etc.
-
Right value reference: in fact, it is also a reference bound to the right value. You can obtain the right value reference through &.
- Example of lvalue reference:
int a=10; //Non constant lvalue (with definite storage address and variable name) const int a1=20; //Constant lvalue (with definite storage address and variable name) const int a2=20; //Constant lvalue (with definite storage address and variable name) //Non constant right value reference int &&b1=a; //Error: A is a non constant lvalue and cannot be bound by non constant lvalue reference int &&b2=a1; //Error: a1 is a constant lvalue and cannot be bound by a non constant lvalue reference int &&b3=10; //Correct, 10 is a non constant right value, which can be bound by non constant right value reference int &&b4=a1+a2; //Error, (a1+a2) is a constant right value and cannot be bound by a non constant right value reference //Constant right value reference const int &&c1=a; //Error, a is a non constant lvalue and cannot be bound by a constant lvalue reference const int &&c2=a1; //Error: a1 is a constant lvalue and cannot be bound by a constant lvalue reference const int &&c3=a+a1; //Correct, (a+a1) is a non constant right value, which can be bound by a constant right value reference const int &&c4=a1+a2; //Correct, (a1+a2) is a constant right value and cannot be bound by a constant right value reference
- Summary: non constant right value references can only be bound to non constant right values; Constant R-value references can be bound to non constant r-values and constant r-values.
It can be found from the above that constant lvalue references can be bound to right values, but right value references cannot bind any type of lvalues. What should I do if I want to bind lvalues with right value references?
-
C++11 provides a standard library move function to obtain the right value reference bound to the left value, that is, directly call std::move to tell the compiler to treat the left value as the same type of right value, but the called left value can no longer be used.
- std::move() function example
int a=10; //Non constant lvalue (with definite storage address and variable name) const int a1=20; //Constant lvalue (with definite storage address and variable name) //Non constant right value reference int &&d1=std::move(a); //Correct, convert the non constant lvalue a to the non constant lvalue, which can be bound by the non constant lvalue reference int &&d2=std::move(a1); //Error: convert constant lvalue a1 to constant lvalue, which cannot be bound by non constant lvalue reference //Constant right value reference const int &&c1=std::move(a); //Correctly, convert the non constant lvalue a to the non constant lvalue, which can be bound by the constant lvalue reference const int &&c2=std::move(a1); //Correct, convert the constant lvalue a1 to the constant lvalue, which can be bound by the constant lvalue reference
As like as two peas, the compiler can use the std::move to convert the left value to the same type of right value.
3, The difference between right value reference and left value reference
-
1. The lvalue reference is bound to an object with a certain storage space and variable name, and the object still exists after the expression ends;
-
2. The R-value reference is bound to temporary objects such as expressions that require conversion, literal constants, and expressions that return r-values. After the assignment expression is completed, the object will be destroyed.
-
3. After lvalue reference, you can use alias to modify lvalue object; The value of the right value reference binding cannot be modified.
4, Reason for introducing R-value reference
- 1. Replace the copy of the object that needs to be destroyed to improve efficiency: in some cases, it is necessary to copy an object and then destroy it. For example, for the copy of temporary class objects, first copy the resources of the old memory to the new memory, and then release the old memory. After introducing the right value reference, the new object can directly use the old memory and destroy the original object, In this way, the use of memory and computing resources is reduced, so as to improve the operation efficiency;
- 2. Move class objects that contain resources that cannot be shared, such as IO and unique_ Classes such as PTR contain resources that cannot be shared (such as IO buffers and pointers). Therefore, these class objects cannot be copied but can be moved. In this case, you need to call std::move to convert the left value to the right value, and then reference the right value.
In this example, use the code in other articles to illustrate the benefits of introducing right value reference: Click on this link for the following original code article
There are the following string Class, which implements the overload of copy constructor and assignment operator.
class MyString { private: char* _data; size_t _len; void _init_data(const char *s) { _data = new char[_len + 1]; memcpy(_data, s, _len); _data[_len] = '\0'; } public: MyString() { _data = NULL; _len = 0; } MyString(const char* p) { _len = strlen(p); _init_data(p); } MyString(const MyString& str) { _len = str._len; _init_data(str._data); std::cout << "Copy Constructor is called! source: " << str._data << std::endl; } MyString& operator=(const MyString& str) { if (this != &str) { _len = str._len; _init_data(str._data); } std::cout << "Copy Assignment is called! source: " << str._data << std::endl; return *this; } virtual ~MyString() { if (_data != NULL) { std::cout << "Destructor is called! " << std::endl; free(_data); } } }; int main() { MyString a; a = MyString("Hello"); std::vector<MyString> vec; vec.push_back(MyString("World")); }
Operation results:
Copy Assignment is called! source: Hello Destructor is called! Copy Constructor is called! source: World Destructor is called! Destructor is called! Destructor is called!
A total of 2 copies have been executed. MyString("Hello") and MyString("World") are temporary objects. After the temporary objects are used, they will be destructed immediately, and the requested memory resources will be free in the destructor. If you can directly use the resources that the temporary object has applied for and cancel the release of resources in its destructor, you can save resources and save the time of resource application and release. This is the purpose of defining transfer semantics.
Realize the right value reference (i.e. reuse temporary objects) by adding the overload of defining transfer constructor and transfer assignment operator:
MyString(MyString&& str) { std::cout << "Move Constructor is called! source: " << str._data << std::endl; _len = str._len; _data = str._data; str._len = 0; str._data = NULL; // ! Prevent memory from being freed in destructors } MyString& operator=(MyString&& str) { std::cout << "Move Assignment is called! source: " << str._data << std::endl; if (this != &str) { _len = str._len; _data = str._data; str._len = 0; str._data = NULL; // ! Prevent memory from being freed in destructors } return *this; }
Operation results:
Move Assignment is called! source: Hello Move Constructor is called! source: World Destructor is called! Destructor is called!
It should be noted that the R-value reference does not prevent the compiler from releasing the temporary object after it is used, so it will be used in the overloaded functions of the transfer constructor and transfer assignment operator_ The value of data is NULL, and it is guaranteed in the destructor_ data != NULL will be released.