A very important concept introduced in C ++ 11 is right value reference. Understanding the right value reference is the basis for learning "Move Semantics. To understand the right value reference, you must first distinguish the left value from the right value.
The most common misunderstanding of the left and right values is that the left side of the equal sign is the left side, and the right side of the equal sign is the right side. Both the left and right values are for expressions. The left value is a persistent object that exists after the expression ends. the right value is a temporary object that no longer exists at the end of the expression. A convenient way to distinguish the left value from the right value is to see if the address can be obtained for the expression. If yes, it is the left value; otherwise, it is the right value. The following are some examples.
Int A = 10 ;
Int B = 20 ;
Int * PFLAG = &;
Vector < Int > Vcttemp;
Vcttemp. push_back ( 1 );
String Str1 = " Hello " ;
String Str2 = " World " ;
Const Int & M = 1 ;
Excuse me, A, B, A + B, A ++, ++ A, PFLAG, * PFLAG, vcttemp [0], 100, string ("hello"), str1, is str1 + str2 and m the Left or Right values?
Both A and B are persistent objects (addresses can be obtained) and are left values;
A + B is a temporary object (the address cannot be obtained) and is the right value;
A ++ extracts a copy of Persistent Object a first, adds 1 to the value of Persistent Object A, and returns the copy, the copy object is a temporary object (the address cannot be obtained), so it is the right value;
++ A adds 1 to the value of Persistent Object A and returns the permanent object a itself (which can be an address). Therefore, it is the left value;
Both PFLAG and * PFLAG are persistent objects (addresses can be obtained), which are left values;
Vcttemp [0] calls the overloaded [] operator, while the [] OPERATOR returns an int &, which is a persistent object (the address can be taken for it) and is the left value;
100 and string ("hello") are temporary objects (addresses cannot be obtained), which are the right values;
Str1 is a persistent object (the address can be obtained), which is the left value;
Str1 + str2 calls the + operator, while the + operator returns a string (the address cannot be obtained), so it is the right value;
M is a constant reference that refers to a right value, but the reference itself is a persistent object (which can be an address), which is the left value.
The left and right values are clearly distinguished. Let's take a look at the reference of the Left value. The left value reference can be divided into the constant left value reference and the constant left value reference based on its modifier.
The reference of the constant left value can only be bound to the constant left value. It cannot be bound to the constant left value, the constant right value, or the constant right value. If you allow binding to the left and right values of constants, the reference of the Left value of a constant can be used to modify the left and right values of a constant, which obviously violates the meaning of a constant. If you can bind a non-constant right value to a temporary object, this may cause a very dangerous situation, A non-constant reference to the left value may use a temporary object that has been destroyed.
Constant left value reference can be bound to all types of values, including the constant left value, constant left value, constant right value, and constant right value.
We can see that when the left value reference is used, we cannot distinguish whether the bound value is a very large right value. So, why do we need to distinguish the right value of a non-Constant? What are the advantages of differentiation? This involves a famous performance issue in C ++-copying temporary objects. Consider the following Code :
Vector < Int > Getallscores ()
{
Vector < Int > Vcttemp;
Vcttemp. push_back ( 90 );
Vcttemp. push_back ( 95 );
Return Vcttemp;
}
When vector <int> vctscore = getallscores () is used for initialization, three constructors are actually called. Although some compilers can use RVO (Return Value optimization) for optimization, the optimization can only be performed under certain conditions. As you can see, a common function call above causes the overhead of copying constructor and destructor twice due to temporary object copying. Of course, we can also modify the function form to void getallscores (vector <int> & vctscore), but this is not necessarily the form we need. In addition, consider the following string connection operations:
String S1 ( " Hello " );
String S = S1 + " A " + " B " + " C " + " D " + " E " ;
During s initialization, a large number of temporary objects are generated and a large number of string copy operations are involved, which obviously affectsProgramEfficiency and performance. How can this problem be solved? If we can determine that a value is a very high right value (or a left value that will not be used later), when we copy a temporary object, you do not need to copy the actual data, but just "steal" the pointer to the actual data (similar to auto_ptr in STL, it will transfer ownership ). The reference to the right value introduced in C ++ 11 can be used to identify an extraordinary right value. In C ++ 11, use & to indicate the reference of the Left value, and use & to indicate the reference of the right value, for example:
Int & A = 10 ;
The right value reference can be divided into the constant right value reference and the constant right value reference based on its modifier.
The reference of the extraordinary right value can only be bound to the extraordinary right value, it cannot be bound to the left value of a constant, the left value of a constant, or the right value of a constant. (In vs2010 beta, You can bind the left value of a constant to the left value of a constant, not allowed ). If binding to a constant left value is allowed, it may mistakenly steal the data of a persistent object, which is very dangerous. If binding to the constant left value and the constant right value is allowed, then, the constant's right value reference can be used to modify the constant's left value and constant's right value, which obviously violates the meaning of its constant.
The constant's right value reference can be bound to the constant's right value and constant's right value. It cannot be bound to the constant's left value or constant's left value (same as the reason ).
With the concept of right value reference, we can use it to implement the following cmystring class.
Class Cmystring
{
Public :
// Constructor
Cmystring ( Const Char * Pszsrc = NULL)
{
Cout < " Cmystring (const char * pszsrc = NULL) " <Endl;
If (Pszsrc = NULL)
{
M_pdata = New Char [ 1 ];
* M_pdata = ' \ 0 ' ;
}
Else
{
M_pdata = New Char [Strlen (pszsrc) + 1 ];
Strcpy (m_pdata, pszsrc );
}
}
//Copy constructor
Cmystring (ConstCmystring & S)
{
Cout <"Cmystring (const cmystring & S)"<Endl;
M_pdata =New Char[Strlen (S. m_pdata) +1];
Strcpy (m_pdata, S. m_pdata );
}
// move constructor
cmystring (cmystring & S)
{< br> cout " cmystring (cmystring & S) " m_pdata = S. m_pdata;
S. m_pdata = NULL;
}
//Destructor
~ Cmystring ()
{
Cout <"~ Cmystring ()"<Endl;
Delete [] m_pdata;
M_pdata = NULL;
}
// Copy assignment function
Cmystring & Operator = ( Const Cmystring & S)
{
Cout < " Cmystring & operator = (const cmystring & S) " <Endl;
If ( This ! = & S)
{
Delete [] m_pdata;
M_pdata = New Char [Strlen (S. m_pdata) + 1 ];
Strcpy (m_pdata, S. m_pdata );
}
Return * This ;
}
// move value assignment function
cmystring & operator = (cmystring & S)
{< br> cout " cmystring & operator = (cmystring & S) " If ( This ! = & S)
{< br> Delete [] m_pdata;
m_pdata = S. m_pdata;
S. m_pdata = NULL;
}< br> return * This ;
}
Private:
Char* M_pdata;
};
As you can see, we have added the constructor and value assignment functions of the move version. After the move version is added, what is the impact on the Automatic Generation rules of the class? The only effect is that if you provide the move constructor version, no default constructor will be generated. In addition, the compiler will never automatically generate the constructor and value assignment functions of the move version. They need to be manually explicitly added.
After a move constructor and a value assignment function are added with an overload, which overload version should be used for a function call? The following are the three rules listed based on the Decision priority:
1. A constant value can only be bound to a constant reference and cannot be bound to a constant reference.
2. The left value is preferentially bound to the left value reference, and the right value is preferentially bound to the right value reference.
3. The constant value is preferentially bound to the constant reference.
When a non-weighted right value is input to the constructor or value assignment function, the constructor or value assignment function of the move version can be called according to the Decision Rule given above. The constructor or value assignment function in the move version directly moves the Internal Data Pointer (because it is a non-constant right value and is a temporary object, moving the pointer to its internal data will not cause any problems, and it will be destroyed immediately. We just reuse its memory), saving the cost of copying data.
Note that the copy constructor can be implemented by directly calling * This = s, but the move constructor cannot. This is because in the move constructor, although S is a very large reference to the right value, it is itself a left value (A Persistent object, which can be an address ), therefore, when * This = s is called, the copy assignment function is used instead of the move assignment function, which is inconsistent with the semantics of the move constructor. To make the semantics correct, we need to bind the left value to the reference of the extraordinary right value. c ++ 11 provides the move function to implement this conversion, therefore, we can change it to * This = move (s), so that the move constructor will call the move value assignment function.