Translation Move semantics and rvalue references in c++11

Source: Internet
Author: User

Solemnly declare: This article is the author of the online translation of the original, some do add instructions, the title of the original author!

Address: Http://www.cprogramming.com/c++11/rvalue-references -and-move-semantics-in-c++11.html

C + + has been working to generate fast programs. Unfortunately, until c++11, there has been a stubborn disease that lowers the speed of C + + programs: the creation of temporary variables. Sometimes these temporary variables can be optimized by the compiler (for example, return value optimizations), but this is not always feasible, which often results in expensive object replication costs. What was I talking about?

Let's take a look at the following code:

1 #include <iostream> 2 #include <vector> 3 using namespace std; 4  5 vector<int> doublevalues (const vector<int>& v) 6 {7     vector<int> new_values (V.size () ); 8     for (Auto ITR = New_values.begin (), End_itr = New_values.end (); ITR! = End_itr; ++itr) 9     {         New_values.pus H_back (2 * *itr);     }12     return new_values;13}14 int main () + {     vector<int> v;18 for     (int i = 0; I < 2; i++)         v.push_back (i)     }22     v = doublevalues (v); 23}

(I note: the vector<int> doublevalues (const vector<int>& v) function in the code is multiplied by 2 for the value in vector v, stored in another vector and returned.) If we add the following code in line 22nd to the value in output V, we find that the value in V does not change, it is 0.

for (auto x:v)    cout << x << Endl;

Should be changed to this:

1 #include <iostream> 2 #include <vector> 3 using namespace std; 4  5 vector<int> doublevalues (const vector<int>& v) 6 {7     vector<int> new_values; 8     for (auto x:v) 9         new_values.push_back (2 * x);    Ten     return new_values;11}12 int main () {     vector<int> v;16 for     (int i = 0; i < 2; i++)     {         v.push_back (i);     }20     v = doublevalues (v); 21}

In addition, I do not recommend the use of vectors like the original author, because Push_back will change the original vector memory distribution and size, there will be some unpredictable errors, code is not robust. )

If you have done a lot of high-performance optimization work, I am sorry for the pain caused by this stubborn disease. If you're not doing this kind of optimization, well, let's work on why this code is a nightmare in c++03 (the next section is explaining why C++11 is better in this area). This problem is related to copying variables, and when the Doublevalues () function is called, it constructs a temporary vector (that is, new_values) and populates the data. It is not efficient to do this alone, but if you want to keep the original vector pure, we need another copy. Think of the doublevalues () function returning what happened?

All data in the New_values must be copied again! Theoretically, there could be a maximum of 2 replication operations here:

1, the temporary variables that occur in the return;

2, occurs at v = doublevalues (v); here.

The first copy operation may be automatically optimized by the compiler (that is, the return value optimization), but the second time to copy the temporary variable to V is unavoidable, because there is a need to reallocate memory space and to iterate over the entire vector.

The example here may be a bit of a fuss. Of course, you can avoid this problem in other ways, such as by pointers or by passing an already filled vector. The fact that both of these programming methods are reasonable. In addition, a method that returns a pointer requires at least one memory allocation, and avoids memory allocation as one of the C + + design goals.

Worst of all, the value returned by the function doublevalues () throughout the process is a temporary variable that is no longer needed. When executed to V = doublevalues (v) Here, once the copy operation is complete, the result of V = doublevalues (v) is discarded. In theory, the entire copy process can be avoided and only the pointer to the temporary vector is saved to V. In fact, why don't we move the object? In c++03, regardless of whether the object is temporary, we have to run the same code in the copy operator = or copy constructor, no matter where the value comes from, so "stealing (pilfering)" is not possible here. In c++11 this kind of behavior is possible!

This is the right and move semantics! When you use temporary variables that will be discarded, the move semantics can avoid unnecessary copies of the copy, and the resources from the temporary variables can be used elsewhere. Move semantics are c++11 new features, called rvalue references , and you want to see how this can benefit programmers. First, let's say what the right value is, and then say what the rvalue reference is, and finally we'll go back to the move semantics and see how the Rvalue reference is implemented.

Rvalue and Lvalue-an enemy or a friend?

There are left and right values in C + +. An lvalue is an expression that can get an address-a memory address locator address-in essence, an lvalue can provide a semi-permanent memory. We can assign a value to an lvalue, for example:

1 int a;2 a = 1; Here, A was an lvalue

You can also make an lvalue not a variable, such as:

1 int x;2 int& GETREF () 3 {4    return x;5}6  7 getRef () = 4;

Here Getref () returns a reference to a global variable, so its return value is stored in memory in a permanent position. You can use GETREF () just like you would with a normal variable.

  If an expression returns a temporary variable, the expression is the right value . For example:

1 int x;2 int getval () 3 {4     return x;5}6 getval ();

Here Getval () is the right value, because the return value x is not a reference to the global variable x, just a temporary variable. If we use objects instead of numbers, this will be a bit of a meaning, such as:

1 string GetName () 2 {3     return "Alex"; 4}5 getName ();

GetName () Returns a string object constructed inside the function that you can assign to the variable:

String name = GetName ();

At this point you are using the temporary variable, getName () is the right value.

The right value of a temporary object that detects an rvalue reference involves a temporary object-just like the doublevalues () return value. Wouldn't it be nice if we knew very well that the value returned from an expression was temporary and knew how to write overloaded temporary objects? Why, the truth is true. What is an rvalue reference is a reference to a temporary object that is bound to it! Before c++11, if you had a temporary object, you would need to bind with a "formal (regular)" or "lvalue reference (Lvalue Reference)", but what if the value is const? Such as:
1 Const string& name = GetName (); OK2 string& name = GetName (); Not OK

It is obvious that you cannot use a "variable (mutable)" Reference here, because if you do, you will be able to modify the object that is about to be destroyed, which is quite dangerous. By the way, saving a temporary object in a const reference ensures that the temporary object will not be destroyed immediately. This is a good C + + programming habit, but it is still a temporary object that cannot be modified.

In c++11, however, a new reference, called Rvalue reference, is introduced that allows binding a mutable reference to an rvalue, not a left-hand value. In other words, an rvalue reference focuses on detecting whether a value is a temporary object. the rvalue uses && syntax instead of &, and can be const and non-const, just like an lvalue reference, although you rarely see a const lvalue reference.

1 Const string&& name = GetName (); OK2 string&& name = GetName (); Also ok-praise be!

So far everything is working well, but how is this done? The most important difference between lvalue and rvalue references is the use of the left and right values of the function arguments. Take a look at the following two functions:

1 printreference (const string& str) 2 {3     cout << str;4}5  6 printreference (string&& str) 7 {8
   cout << Str;9}

The behavior of the function printreference () Here is interesting: printreference (const string& STR) accepts any parameters, both Lvalue and rvalue, regardless of whether the left or right values are mutable. Printreference (string&& str) accepts any parameter except for a mutable rvalue reference. In other words, write as follows:

1 string Me ("Alex"); 2 printreference (  me);//calls the first Printreference function, taking an lvalue reference 3 Printreference (GetName ()); Calls the second Printreference function, taking a mutable rvalue reference

Now we should have a way to determine whether a reference is used on a temporary object or a non-temporary object. The right-hand reference version of the method is like entering the secret backdoor of the club (boring club, I guess), if it is a temporary object, it can only be entered. Now that we have a way to determine if an object is a temporary object, how do we use it?

Move constructor and move assignment operator

When you use Rvalue references, the most common pattern is to create a move constructor and a move assignment operator (following the same principle). The move constructor, like the copy constructor, creates a new instance of the original instance object as a parameter with an instance object. The move constructor then avoids memory allocations because we know that it has provided a temporary object instead of copying the entire object, just "moving". If we have a simple Arraywrapper class, the following:

1 class Arraywrapper 2 {3     public:4         arraywrapper (int n) 5             : _p_vals (new int[n]) 6             , _size (n) 7
   
    {} 8         //copy Constructor 9         arraywrapper (const arraywrapper& Other)             : _p_vals (New int[Other._size
    ]) One             , _size (other._size)         {(             int i = 0; i < _size; ++i)                 [_p_vals[] er._p_vals[i];16             }17         }18         ~arraywrapper ()         {             Delete [] _p_vals;21         }22     private:23     int *_p_vals;24     int _size;25};
   

Note that the copy-copy constructor here allocates memory each time and copies each element in the array. For a copy operation that is such a huge amount of work, let's add a move copy constructor for efficient performance.

1 class Arraywrapper 2 {3 public:4     //default constructor produces a moderately sized array 5     arraywrapper () 6< c2/>: _p_vals (New int[)) 7         , _size (8)     {} 9     arraywrapper (int n) One         : _p_vals (New int[N ])         , _size (n)     {}14     //Move constructor16     arraywrapper (arraywrapper&& Other)         : _p_vals (other._p_vals  )         , _size (other._size)     {         other._p_vals = null;21     }     $//copy CONSTRUCTOR24     arraywrapper (const arraywrapper& Other)         : _p_vals (New int[ Other._size  ])         , _size (other._size) (         int i = 0; i < _size; ++i)             _p _vals[i] = other._p_vals[i];31         }32     }33     ~arraywrapper ()     {+         delete [] _p_vals;36     } Notoginseng  private:39     int *_p_vals;40     int _size;41};

The move constructor is actually simpler than the copy constructor, which is pretty good. Focus on the following two points:

1. Parameter is a non-const rvalue reference

2. Other._p_vals should be null

The 2nd above is the interpretation of the 1th, that is, if we use a const rvalue reference, you cannot set other._p_vals to null. But why would you set other._p_vals to null? The reason is that the destructor is called when the temporary object leaves its scope, just like all other C + + objects. When the destructor is called, the _p_vals is freed. Here we just copy the _p_vals, if we do not set _p_vals to Null,move is not really "moving", but rather copy, once we use the freed memory will cause the run to collapse. the meaning of the move constructor is to avoid copying operations by altering the original temporary object.

Repeat again, the move constructor is overloaded so that the move constructor is called only if it is a temporary object, and only temporary objects can be modified. This means that if the return value of the function is a const object, the copy constructor is called instead of the move constructor, so do not write like this:

1 const arraywrapper Getarraywrapper (); Makes the move constructor useless, the temporary is const!

There are some situations in the move constructor that we have not discussed, such as a field in a class that is also an object. Observe the following class:

1 class MetaData 2 {3 public:4     MetaData (int size, const std::string& name) 5         : _name (name) 6         , _size ( Size) 7     {} 8   9     //Copy constructor10     MetaData (const metadata& Other)         : _name (other._name) 12         , _size (other._size)     {}14     /move constructor16     MetaData (metadata&& Other)         : _name (other._name) 18         , _size (other._size)     {}20     std::string getName () const {return _name;}     GetSize Int () const {return _size;}     private:24     std::string _name;25     int _size;26};

Our array has the field name and size, so we should change the definition of Arraywrapper as follows:

 1 class Arraywrapper 2 {3 Public:4//default constructor produces a moderately sized array 5 arraywrapper () 6         : _p_vals (New int[) 7, _metadata ("Arraywrapper") 8 {} 9 arraywrapper (int n) 11     : _p_vals (new int[n]), _metadata (N, "Arraywrapper"), {}14//move Constructor16 Arraywrapper (arraywrapper&& Other): _p_vals (Other._p_vals), _metadata (Other._metadata)  {other._p_vals = null;21}22//Copy constructor24 arraywrapper (const arraywrapper&         Other): _p_vals (New int[other._metadata.getsize ()), _metadata (Other._metadata) 27 {28         for (int i = 0; i < _metadata.getsize (); ++i) {_p_vals[i] = other._p_vals[i];31 }32}33 ~arraywrapper () {+ delete [] _p_vals;36}37 private:38 int *_p_vals;39 Me Tadata _metadata;40};

So that's all you got? Just call metadata's move constructor in Arraywrapper, it's all natural, right? The problem is that it doesn't work! The reason is simple: the other in the move constructor is an rvalue reference. This should be the right value, not the Rvalue reference! If it is an lvalue, the copy constructor is called instead of the move constructor. It's weird, it's a little bit around, right-I know. Here's a way to differentiate: the right value is a creation of an expression that will be destroyed later. When the temporary object is about to be destroyed, we pass it into the move constructor, which is equivalent to giving it a second life, which is still valid in the new scope. This is the place where the right value appears. In our constructor, the object has a Name field, which is always valid inside the function. In other words, we can use it more than once in a function, and the temporary variable defined inside the function is always valid inside the function. The left value can be positioned, and we can access an lvalue at a location in memory. In fact, we might want to use it later in the function. If the move construct is called, then we have an rvalue reference object, so we can use the "moving" object.

1//Move Constructor2 Arraywrapper (arraywrapper&& Other) 3     : _p_vals (other._p_vals  ) 4     , _metadata (Other._metadata) 5 {6     //If _metadata (Other._metadata) calls the move constructor, using 7     //Other._metadata Here would is extremely dangerous!8     other._p_vals = null;9}

The last case: both Lvalue and rvalue references are lvalue expressions. What is not necessary is that an lvalue reference must be a const bound to an rvalue, whereas an rvalue reference can always bind a reference to the right value. is similar to the difference between the pointer and the content pointed to by the pointer. The value used is the right value, but when we use the right value itself, it becomes an lvalue.

Std::move

So what are the tricks to handle this? We can use Std::move, which is included in <utility>. If you want to convert an lvalue to an rvalue, you can use Std::move, where the std::move itself does not move anything, it simply converts the lvalue to the right value, or it can be implemented by calling the move constructor. Take a look at the following code:

1 #include <utility>//For std::move2//Move Constructor3 Arraywrapper (arraywrapper&& Other) 4      : _p_va LS (other._p_vals  ) 5       , _metadata (Std::move (other._metadata)) 6 {7         other._p_vals = null;8}

Similarly, the metadata should also be modified:

1 MetaData (metadata&& other) 2     : _name (Std::move (Other._name))//Oh, blissful efficiency3     : _size (OT Her._size) 4 {}
Assignment operator

As with the move constructor, we should also have a move assignment operator that is written in the same way as the move constructor.

Move constructor and Implicit constructor

As you know, in C + + as long as you manually declare the constructor, the compiler will no longer generate a default constructor for you. The same is true here: adding a move constructor for a class requires you to define and declare a default constructor. Also, declaring the move constructor does not prevent the compiler from generating an implicit copy constructor for you, and declaring the move assignment operator does not prevent the compiler from creating a standard assignment operator.

How the Std::move works

You might wonder: How do you write a function like Std::move? How is an rvalue reference converted to an lvalue reference implemented? Perhaps you have guessed the answer, that is typecasting. Std::move's actual declaration is more complex, but its core idea is to static_cast to rvalue references. This means that you do not really need to use move--but you should do so so that you can express your meaning more clearly. The fact that conversion is necessary is a good thing, which prevents you from accidentally converting an lvalue to an rvalue because that will cause unexpected move to occur, which is quite dangerous. You must display the Std::move (or a transform) to convert the Lvalue to an rvalue reference, and the rvalue reference will not bind its own left-hand value.

The function returns an explicit right-value reference

When should you write a function that returns an rvalue reference? What does the function return rvalue reference to mean? Is the function of returning an object by value a right value?

Let's first answer the second question: returning an explicit rvalue reference is different from returning an object by value (by value). Let's take a look at the following example:

1 int x;  2   3 int getInt () 4 {5     return x; 6} 7   8 int && getrvalueint () 9 {ten     //Notice that it's fine to Move a primitive type--remember, Std::move is just a cast11     return std::move (x); 12}

Obviously in the first case, although the getint () is actually the right value, the copy operation is still performed on X. We can write an auxiliary function to see:

1 void printaddress (const int& v)//const ref to allow binding to Rvalues2 {3     cout << reinterpret_cast< Const void*> (& V) << endl;4}5  6 printaddress (GetInt ()); 7 printaddress (x);

Running discovery, the X addresses printed are significantly different. On the other hand:

1 printaddress (Getrvalueint ()); 2 printaddress (x);

The X-Address printed is the same, because Getrvalueint () explicitly returns an rvalue.

So it is significantly different to return an rvalue reference than to return an rvalue reference. This difference is most noticeable when you return an object that already exists, rather than a temporary object created inside the function (the compiler might make a return value optimization for you to avoid the copy operation).

The question now is whether you need to do this. The answer is: probably not. In most cases, you are most likely to get a dangling (dangling) rvalue (in which case the reference exists, but the temporary object it refers to has been destroyed). The risk level for this situation is similar to the lvalue that the referenced object already does not exist. Rvalue references do not always guarantee that objects are valid. Returning an rvalue reference primarily makes sense for this particular case: you have a member function that returns a field in the class by Std::move.

Move semantics and standard libraries

Back in the first example, we are using vectors, but we do not control the vector, and we do not know whether the vector is a move constructor or a move assignment operator. Fortunately, the standard board has added move semantics to the standard library, which means you can efficiently return vectors, maps, strings, and any standard library objects you want to return, taking advantage of the move semantics.

Objects that can be moved in an STL container

In fact, the standard library is doing even better. If you use the move semantics in your object by creating a move constructor and a move assignment operator, when you store these objects in an STL container, the STL will automatically use Std::move to take advantage of the move semantics to avoid the copy operation under efficiency.

Translation Move semantics and rvalue references in c++11

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.