Right value reference and its function

Posted by davey10101 on Sun, 16 Jan 2022 02:37:26 +0100

I. what are left and right values?

Before mastering the right value reference, you must first know what the right value is. Since there is an right value, there must be an left value. When we assign a=b, the value that can be placed to the left of the = sign is the left value, and the reverse is the right value.

So what value can be used as an lvalue? Obviously, variables can be used as lvalues. What value cannot be an lvalue? Obviously, constants, expressions, function return values, etc. cannot be used as left values, that is, right values. Obviously, as lvalues, they can be saved for a long time, corresponding to being stored in memory; However, constants, expressions, function return values, etc. are temporary values, which are stored in registers. It can be concluded that:

  1. Lvalue: it can be saved for a long time. It can exist in the value on the left of = and can take the address;
  2. Right value: temporary value. It cannot exist in the value on the left of = and cannot take the address.
int a = 9, b = 8;
a = 8, b = 0;       // a. B is the left value
// a + 4 = 5;       //  Error a + 4 is a right value and a + 4 is a temporary object
// -a = 4;          //  Error - A is an rvalue and a temporary object
(a) = 5;            // Correct (a) is an lvalue and returns a
++a = 3;            // Correct + + A is an lvalue, which can be understood as a first + 1, and then returns a itself, that is, the whole expression is a
// a++ = 3;         //  Error a + + is a right value, which can be understood as int tmp = a;a = a +1;return tmp; A temporary variable is returned

2, What are lvalue and rvalue references?

A reference to an lvalue is an lvalue reference, and a reference to an rvalue is an rvalue reference. It should be noted that the reference is an alias of the pair value, so it will not produce a copy. At the same time, the reference itself is also a variable, so it is also an lvalue. As follows:

int &t = a;         // a is an lvalue, so it can be assigned to an lvalue reference
// int &t1 = 3;     //  Error 3 is a temporary value, which is an R-value and cannot be assigned to an l-value reference
// int &&t = a;     //  Error a is an lvalue and cannot be assigned to an lvalue reference
int &&t = 3;        // sure
int &&t = -a;       // sure
// int &t = -a;     //  may not
// int &&t1 = t;    //  No, t itself is an lvalue

3, R-value reference

R-value reference is a new feature of C++11. The reason why R-value reference is introduced is to improve efficiency. As shown below:

class A
{
public:
	A(size_t N):m_p(new char[N])
	{
	}
	A(const A & a)
	{
		if (this != &a)
		{
			delete[]m_p;
			m_p = new char[strlen(m_p) + 1];
			memcpy(m_p, a.m_p, strlen(m_p) + 1);
		}
	}
	~A()
	{
		delete []m_p;
	}

private:
	char *m_p = nullptr;
};

A createA(size_t N)
{
	return A(100);
}

void func(A a)
{
	//
}

int main()
{
	func(createA(100));

	system("pause");
	return 0;
}

This will lead to a large number of calls to the constructor of A. regardless of compilation optimization, the original implementation is as follows:

  1. createA(100), execute A(100) and call the A(size_t) constructor once;
  2. Exit createA, temporarily construct A(100), release and call the destructor once;
  3. Assigning a return value will call the copy constructor once;
  4. The return value is passed into func to call the copy constructor once;
  5. After func runs, release the formal parameters and call the A destructor once;
  6. The return value is released after use, and the A destructor is called once;

It can be seen from the above that there are a lot of construction and destruct calls, but what we do is to temporarily construct an A(100) for func. So can you always give a copy of temporary A(100) to func? The answer is an R-value reference. As follows:

class A
{
public:
	A(size_t N):m_p(new char[N])
	{
	}
	~A()
	{
		delete []m_p;
	}

private:
	char *m_p = nullptr;
};

A&& createA(size_t N)
{
	return (A&&)A(100);
}

void func(A&& a)
{
	//
}

int main()
{
	func(createA(100));

	system("pause");
	return 0;
}

We cast the temporary A(100) into an R-value reference, and the func formal parameter is also an R-value reference, that is, we extend the temporary object into func, avoiding other construction and destruct calls in the middle, which improves the efficiency.

Notice that we have removed the copy constructor of A, because it is no longer used. If the original is written, removing the copy constructor will crash, because the default copy constructor will be called automatically, which is A shallow copy. The intermediate temporary object will delete the public memory in advance, and the subsequent object will be repeatedly deleted if it is released again, resulting in A crash.

4, Convert left value to right value (std::move)

An R-value reference can only be bound to an R-value. Can an R-value reference be bound to an l-value? Yes, a very simple way to write it is forced conversion, as follows:

int main()
{
	int a = 3;
	int &&t = (int &&)a;
	t = 9;
	cout << a << endl;  // a = 9

	system("pause");
	return 0;
}

In fact, C++11 provides a more elegant conversion function STD:: move. STD:: move (a) whether a is a left value or a right value will be converted to a right value. As follows:

int main()
{
	int a = 3;
	int &&t = std::move(a);
	int &&t2 = std::move(3);

	system("pause");
	return 0;
}

5, Implementation principle of std::move (forced conversion)

You can open the std::move source code as follows:

template<class _Ty> inline
	constexpr typename remove_reference<_Ty>::type&&
		move(_Ty&& _Arg) _NOEXCEPT
	{	// forward _Arg as movable
	return (static_cast<typename remove_reference<_Ty>::type&&>(_Arg));
	}

As you can see, move is passed in_ Arg value, cast to typename remove_ reference<_ Ty >: Type & & type, then typename remove_ reference<_ Ty >:: what is type? Then look down.

template<class _Ty>
	struct remove_reference
	{	// remove reference
	typedef _Ty type;
	};

template<class _Ty>
	struct remove_reference<_Ty&>
	{	// remove reference
	typedef _Ty type;
	};

template<class _Ty>
	struct remove_reference<_Ty&&>
	{	// remove rvalue reference
	typedef _Ty type;
	};

Originally, remove_reference is a class template. The first type is the incoming type itself; The second class template parameter is an lvalue reference, and type is the removed reference type; The third class template parameter is the right value reference, and the type is the type without the reference. You can also know remove from its own literal meaning_ The function of reference is to get the original type by removing the reference.

We go back to move again. Move returns typename remove_ reference<_ Ty >:: Type & &, the right value reference of the original type. So far, the function of move is very clear: it is to cast the incoming value into the right value reference of the original value type.

Vi. general references

If you are careful enough, you will find that the move parameter is_ Ty & &, in the form of an R-value reference, why does an error not occur when an l-value is passed in? This involves generic references.

(to be continued...)

        

reference resources:

https://covariant.cn/2020/02/25/uniref-in-cpp/

https://avdancedu.com/a39d51f9/

https://blog.csdn.net/weixin_40539125/article/details/89578720?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

        

Topics: C++