The
Right value reference (and its supported move semantics and perfect forwarding) is one of the most significant language features that c++0x will join, which can be seen from the list of the features in the C + + state of the evolution.
from a practical standpoint, it is a perfect solution to the problem of temporary object efficiency, which has long been criticized in C + +. From the language itself, it is robust to the defect of the reference type in C + + to the right value of the left value. From the Library designer's point of view, it has brought a sharp weapon to the library designer. From the point of view of the library user, the "free" efficiency promotion can be obtained without a pawn.
in the standard C + + language, the temporary amount (the term right, as it appears on the right side of an assignment expression) can be passed to a function, but can only be accepted as a const & type. This allows the function to distinguish between a true right value or a regular variable passed to const &. Also, because the type is const, the function cannot change the value of the object being passed. C++0X will add a new reference type called the right value reference, which is written as TypeName &&. This type can be accepted as a non-const value, allowing changes to its value. This change will allow some objects to create transfer semantics. For example, a std::vector, in terms of its internal implementation, is a C-type array encapsulation. If you need to create a vector temp or return a vector from a function, you can only store the data by creating a new vector and copying all the data stored in the right value. The temporary vector is then destroyed and the data contained in it is deleted. With a right value reference, a std::vector transfer constructor that refers to the right value of a vector can simply copy the pointer of the C-type array in the right value to the new vector and then empty the right value. There is no array copy, and destroying the empty right value does not destroy the memory that holds the data. Functions that return vectors now only need to return a std::vector<>&&. If the vector does not transfer the constructor, the result will be the same as before: Call its copy constructor with the std::vector<> & parameter. If the vector does have a transfer constructor, then the transfer constructor is invoked to avoid a large amount of memory allocation.
I. Definition
in general, in C + +, the preferred address is the left value with the name. No address, no name for the right value. The right value mainly includes the literal amount, the temporary variable value returned by the function, the expression temporary value, and so on. The right value reference is the type that refers to the right value, and the reference in c++98 is called the left value reference.
If you have the following classes and functions:
Class A
{
private:
int* _p;
};
A returnvalue ()
{return
a ();
}
The return value of ReturnValue () is the right value, which is a temporary variable that is not named. In c++98, only a constant left-value reference can reference this value.
a& a = ReturnValue (); Error:non-const lvalue reference to type ' a ' cannot bind to a temporary of type ' a '
const a& a2 = ReturnValue (); Ok
By using a constant left-value reference, you can extend the life cycle of the returnvalue () return value, but you cannot modify it. C++11 's right value reference came out:
The right value reference is declared by "&&", A3 refers to the return value of ReturnValue (), prolongs its lifecycle, and can modify the temporary value.
Two. Mobile semantics
the right value reference can reference and modify the right value, but in general, it makes no sense to modify a temporary value. In the case of a copy of a temporary value, however, we can avoid a copy of the resource by using the right value reference to move the resource within the temporary value to its own use:
#include <iostream>
Class A
{public
:
A (int A)
: _p (new Int (a))
{
}
//Move constructor Mobile semantics
A (a&& RHS)
: _p (rhs._p)
{
//Empty temporary value resource to avoid multiple releases the right to ownership of resources has shifted
rhs._p = nullptr;
std::cout<< "Move constructor" <<std::endl;
}
Copy constructor copy semantics
A (const a& RHS)
: _p (new int (*rhs._p))
{
std::cout<< "copy constructor" <<std::endl;
}
Private:
int* _p;
A returnvalue () {return a (5);}
int main ()
{
A A = ReturnValue ();
return 0;
}
Run the code and find that the move constructor is invoked (the return value is optimized in g++ and there is no output.) This option can be turned off by-fno-elide-constructors). When you construct an object with the right value, the compiler invokes the move constructor in the form of a (a&& RHS), in which you can implement your own mobile semantics, where the temporary object _p pointing to memory directly to your own, avoiding a resource copy. When resources are very large or construction is time-consuming, efficiency gains will be obvious. If a does not define a move constructor, then, as in c++98, the copy constructor is invoked and the copy semantics are executed. Can not move, but also copy.
Std::move:
C++11 provides a function std::move () to force a left value to the right value:
A A1 (5);
A A2 = std::move (A1);
The above code will call the move constructor when constructing A2, and the A1 _p will be empty because the resource has been moved. The life cycle and scope of the A1 have not changed, and still have to wait until the main function is finished, so subsequent access to A1 _p will cause a run error.
Std::move at first glance is of little use. It is used mainly in two places:
- Help to better implement mobile semantics
- Implement perfect Forwarding (mentioned below)
Consider the following code:
Class B
{public
:
B (b&& rhs)
: _PB (RHS._PB)
{
//How can I move rhs._a to this->_a? C11/>RHS._PB = nullptr;
}
Private:
A _a;
int * PB;
}
For the move constructor for B, because RHS is the right value and is about to be released, we do not just want to move _PB resources, but also want to take advantage of the move constructor of Class A to perform the move semantics of a's resources. The problem, however, is that if we use it directly in the initialization list: _a (rhs._a) will call the copy constructor of a. Because the parameter rhs._a is now a named value and can be fetched. In fact, the parameter RHS of the move constructor of B is also a left value, because it is also named and has an address. This is a very confusing point in the c++11 right value reference: the right value reference, which can accept the right value, is itself a left value.
This point is also mentioned in the perfect forwarding later. Now we can use Std::move to convert the rhs._a to a right value: _a (Std::move (rhs._a)), which will invoke the move construct of a. Implement mobile semantics. Of course here we are sure that rhs._a will not be used after that because RHS is about to be released.
Three. Perfect forwarding
If just for the sake of moving semantics, the right value reference is not necessary to be raised, because when we call the function, we can avoid the generation of the temporary value by passing the reference, although the code is not so intuitive, but the efficiency is not lower than using the right value reference.
Another function of the right value reference is perfect forwarding, where the perfect forwarding occurs in generic programming, passing the template function argument to the next template function called by the function. Such as:
Template<typename t>
void Forward (T-t)
{do
(t);
}
In the code above, we want the forward function to pass the passed-in parameter type to the Do function, that is, the left value received by the forward function, the do receives the left value, the forward receives the right value, and do gets the right value. The code above can forward the parameters correctly, but it is not perfect because the forward performs a copy when the parameter is received.
Given the avoidance of copying, we can pass references, shaped like forward (t& T), but this form of forward does not receive the right value as an argument, such as forward (5). Because the very left value cannot be bound to the right value. Consider a constant left-value reference: Forward (const t& T), this form of Forward can receive any type (constant left-value reference is a universal reference), but because of the addition of a constant modifier, the very literal left-value reference cannot be forwarded correctly:
void do (int& i)
{
//do something ...
}
Template<typename t>
void Forward (const t& t)
{do
(t);
}
int main ()
{
int a = 8;
Forward (a); Error. ' void Do (int&) ': cannot convert argument 1 from ' const int ' to ' int& ' return
0;
}
Based on this situation, we can overload the parameters of the forward and pass the reference of the left value correctly. However, when the Do function argument is a right value reference, Forward (5) still fails to pass correctly because the parameters in the Forward are reference to the left value.
The following is a description of the solutions in c++11.
PS: Reference folding
C++11 introduces a reference folding rule that combines the right value reference to solve the perfect forwarding problem:
typedef const INT T;
typedef t& TR;
tr& v = 1; The actual type of V in c++11 is const int&
As in the code, a reference collapse occurs, and the TR expands to get t& & v = 1 (note that this is not a reference to a right value). Here the t& + & is folded to t&. In more detail, according to the type definition of TR, and the Declaration of V, the following collapse rules occur:
t& + & = t&
t& + && = t&
t&& + & = t& t&&
+ && Amp = t&&
The rules above are simplified to: whenever a left-value reference appears, the rule always collapses to a left-valued reference. The right value reference is collapsed only if there are two right value references.
Talk about forwarding again
So what is the use of the above quoted folding rule for perfect forwarding? We notice that for the t&& type, it and the left value reference are folded to the left value reference, and the right value reference collapses to the right value reference. Based on this feature, we can use t&& as our forwarding function template parameter:
Template<typename t>
void Forward (t&& t)
{do
(static_cast<t&&> (t));
In this way, no matter forward receives the left value, the right value, the constant, the extraordinary amount, t can remain the correct type.
When you pass in the left value reference x&:
void Forward (x& && t)
{do
(static_cast<x& &&> (t));
}
void Forward (x& t)
{do
(static_cast<x&> (t));
}
The static_cast here seems to be unnecessary, and it is actually prepared for the right value reference:
void Forward (x&& && t)
{do
(static_cast<x&& &&> (t));
}
void Forward (x&& t)
{do
(static_cast<x&&> (t));
}
As mentioned earlier, the right value reference, which can receive a right value, is itself a left value because it is named and can be evaluated. So in forward (x&& T), the parameter T is already a left value, at which point we need to convert it to the type that it passed in, which is the right value. Because of the existence of reference folding in static_cast, we can always restore the original type of the parameter.
In c++11,static_cast<t&&> (t) can be replaced by std::forward<t> (t), Std::forward is a function c++11 for perfect forwarding, and STD :: Move like, all through the static_cast to achieve. Our forward function eventually becomes:
Template<typename t>
void Forward (t&& t)
{do
(std::forward<t> (t));
You can test with the following code:
#include <iostream>
using namespace std;
void do (int& i) {cout << "left value reference" << Endl;}
void do (int&& i) {cout << "right value reference" << Endl;}
void do (const int& i) {cout << "constant left value reference" << Endl;}
void do (const int&& i) {cout << "constant right value reference" << Endl;}
Template<typename t>
void Perfectforward (t&& t) {do (forward<t> (t));
int main ()
{
int A;
const int B;
Perfectforward (a); The left value refers to
Perfectforward (Move (a));//Right value refers to
Perfectforward (b); The constant left value refers to
Perfectforward (move (b));//constant Right value reference return
0;
}
Four. Notes
both the left and the left values reference, the right and right values are the same thing, the reference is not a new type, just an alias. This is important for understanding template derivation. For the following two functions
Template<typename t>
void Fun (T t)
{
//do something ...
}
Template<typename t>
void Fun (t& T)
{
//do otherthing ...
}
Fun (t) and Fun (t& T) can all accept left values (references), which differ in the semantics of the parameters, which perform copy semantics, and the latter only to take a new alias. So calling the fun (a) compiler will complain because it doesn't know what semantics you want to perform on a. In addition, for fun (t), because it performs copy semantics, it can also accept the right value. Therefore, calling fun (5) does not cause an error because the left reference cannot reference the right value, so only fun (t) can perform the copy.
Finally, attach the source code of VS Std::move and Std::forward:
Move
template<class _ty>
Inline typename remove_reference<_ty>::type&& Move (_ty& & _arg) _noexcept
{return
(TypeName remove_reference<_ty>::type&&) _arg);
Forward
template<class _ty>
inline _ty&& forward (TypeName;:: type& _arg)
{//forward a lvalue return
(static_cast<_ty&&> (_arg));
Template<class _ty>
Inline _ty&& Forward (TypeName remove_reference<_ty>::type&& _ ARG) _noexcept
{//forward anything
Static_assert (!is_lvalue_reference<_ty>::value, "bad forward Call ");
Return (static_cast<_ty&&> (_arg));