C++11 Mobile Semantics (std::move) and Perfect Forwarding (Std::forward) __c++

Source: Internet
Author: User

Objective

All the means are to solve the existing problems. Std::move and Std::forward are the characteristics of c++11 in order to solve the problems left in c++98/c++0x, although it is more complicated from the understanding, but it is a better way to solve them. left value (lvalue) and right value (rvalue)

The concept of the left value and the right value is actually in the c++0x. Generally speaking, the address can be called the left value, on the contrary, called the right value, C + + does not give a clear definition of the left and right values, from its solution to look like the above definition, of course, we can also be defined as: The name of the object is the left value, no name of the object is the right value.

Class A
{};

A A; A is the left value because it has a clear name and it is legal to &a a.

void Test (a)
{
  __test (a);
}

Test (A ()); A () is the right value because a () produces a temporary object, the temporary object has no name and cannot perform a & fetch operation.

To __test (a); Here, A is the left value, because a is named for the caller here, and the __test internal to a is also able to perform & access operations.
But this a is destroyed soon after the call is completed, after all, it is only a temporary variable.

So it is also possible to know from the above pseudocode that, because of nested calls within a function, the right value on the previous layer can be "turned" to the left value to complete the lower call, meaning that the right value reference, which can accept the right value, is itself a left value. (in order to avoid misleading, the pseudo code does not take the form of void Test (A &a), but it is described below. ) Mobile Semantics (std::move)

There is a section of code:

Class A
{public
:
  A (): Array (new Int[3]{1, 2, 3})
  {
  }
  ~a ()
  {
    if (nullptr!= a)
    {
      Delete [] A;
    }
  }
  A (const a& a)
  {
    std::cout << "Copy construct" << Endl;
  }
  Private:
    int *array{nullptr};

  int main ()
  {
    A A1;
    A A2 (A1);
    return 0;
  }

Output:
Copy Construct

The code above looks fine, but the code also has optimized space: If the array is actually referring to a very large array, then the cost of this copy construction and destruction is very large, and in some scenarios it is not even acceptable to copy the large size object. We have an ideal: any large size object can be copied (copy construction, copy assignment operation) only copy the large size object within its class, thus avoiding the construction of large size objects and the additional costs of destruction, the answer is yes.

Class A
{public
:
  A (): Array (new Int[3]{1, 2, 3})
  {
  }
  ~a ()
  {
    if (nullptr!= a)
    {
      Delete [] A;
    }
  }
  A (a &&a)
  {
    array = A.array;
    A.array = nullptr;
    Std::cout << "Memory Copy construct" << Endl;
  }
Public:
  int *array{nullptr};

A gettempobj ()
{
  a A;
  Std::cout << a.array << Endl;
  return A;
}

int main ()
{
  A A (Gettempobj ());  In fact, this code is not standard, relying on compilers to implement
  std::cout << a.array << Endl;
  return 0;
}

Output:
00bef138
Memory Copy Construct
00bef138

In the above code we seem to have realized that only the memory that is referred to in a is copied. Gettempobj () returns a temporary object (note: VS2012 above the compiler will perform Rvo optimizations, Rvo optimizations are temporarily not included in this article), which is used to initialize a object. The above code prints the address of the array, and from the result we have implemented two objects between the copy construct and only its large size memory. The above code applies to a copy construct between a left value and a right value, which we call a mobile copy construct. Thinking about it, we also want to:

A A1;
A A2 (A1); Error: ' A::a (const A &) ': Attempting to reference a deleted function

The code above cannot be compiled, and we need to display the specified copy constructor (because we implement a mobile copy construct that the compiler does not need to automatically generate a copy constructor) to pass. Even so, in the actual code, we cannot always rely on gettempobj () to return the temporary object (the right value) to achieve the mobile copy construction, need a special solution.

In this kind of scene, Std::move came into being. Std::move has a certain misleading name, but it doesn't move anything.

The new standard holds that: the mobile constructor and the mobile copy function should have different processing to the left and right values, and Std::move is the unified solution. Std::move can convert a left value to a right value reference to achieve a moving copy (the following will say how move is converted), so for a copy between two left values, we can do this:

A A1;
A A2 (std::move (A1)); Correctly

So, implement a class with mobile copy constructs and move copy functions:

Class A
{public
:
  A (): Array (new Int[3]{1, 2, 3})
  {
  }

  ~a ()
  {
    if (nullptr!= a)
    {
      Delete [] A;
    }
  }

  A (a &&a)
  {
  }

  a& operator = (a &&rhs)
  {return
    *this;
  }
};

In fact, in order to ensure the transfer of semantics, we should always remember to use the Std::move transform to have members of resources such as heap memory, file handles, and so on, so that if members support moving constructs, they can implement their mobile semantics. And even if the member does not move the constructor, the version of the constructor that accepts the constant left value will easily implement the copy construct (Std::move will default to copy operations without moving the function) without causing a big problem.

Now there's this code:

A Get ()
{return
  a ();
}

A (a) (a);
A A1 (get ());

In fact, both a and A1 do not start moving functions (moving constructs and moving copies) because the temporary object returned by a () and get () is a right value. So is it possible to impose std::move on this temporary object to invoke the Move function? Here is the reference to the left value and the right value. Right Value reference

The left value reference and the right value reference can refer to the left value and the right value. We know that you can use Std::move to convert a left value to a right value reference in order to invoke the move function. So the question is, can the right value reference use Std::move? The answer is yes. In particular, the left-and right-value references can be bound to each other, but they follow the rule: non-const left-value references can only be bound to non-const-left values; A non-const right value reference can only be bound to a non-const right value, but not to a function template's formal parameters; a const right value reference can be bound to a const right value and a non-const right value, and it has no practical significance (after all, the right value reference is intended to move semantics,

We want a and A1 to implement the call to move functions, and from the above we need to convert the return value of a () and get () to a non const right value to implement the call to move the function, the answer is: Std::move.

Template<typename t>  
Inline typename std::remove_reference<t>::type&& move  
(t& & T)  
{return 
  static_cast<typename std::remove_reference<t>::type&&> (t); 
}  

The rule is deduced according to the template parameter, when the incoming parameter is a left value, T is deduced as T&, and then t& + t&& is deduced as t& in fact, move (&), when the incoming parameter is a right value t&& , according to t&& + t&& deduced as T&&, so at any rate, move must eventually return to t&&. For type derivation:

This rule applies when the template parameter of the function template is T and the function parameter is t&& (right value reference). If the argument is a left value a&, the template parameter T should be inferred to be a reference type a&. (according to the reference folding rule, a& + && => a& and t&& <=> a& t <=> a&) If the argument is a right value a&&, the template parameter T should be inferred as unreferenced type A. (A or a&& + && => a&&, and t&& <=> a&& and so T <=> A or a&& Here mandatory T <=> A).

So our code needs to be modified to invoke the move constructor of a, and so does the move copy function:

A (Std::move (A ()));
A A1 (Std::move ());

Remember the auto_ptr in c++98/0x? Auto_ptr in the "copy" of the time is not strictly a copy of the meaning. "Copy" is to keep the source object unchanged and to copy out a new object based on it. But Auto_ptr's "copy" will "empty" the source object, leaving only an empty shell, is actually a transfer of resource ownership, but the danger of auto_ptr is that it should appear to be copy, actually transfer. Auto_ptr invocation of a member function that has been moved will result in unpredictable consequences, c++11 replaced by Unique_ptr, and Unique_ptr is implemented with move semantics. std::move Keywords: efficiency optimization

The first thing we need to do is solve some problems. The emergence of std::move is to solve the problem of efficiency, reduce unnecessary copy. If such a move constructor exists, the copy-construct behavior of all source objects as temporary objects can be simplified to a moving (move) construct. For ordinary string types, the efficiency difference between std::move and copy construction is to save an O (n) assignment, an O (n) copy operation, an O (1) destructor (the destructor of that temporary object being copied). The efficiency gains here are obvious and significant.

Perfect Forwarding (Std::forward)

When programmers do not write highly generic code, the benefits of perfect forwarding may not be realized in the c++98/0x era, when we need to write generic code, we may encounter some problems of function invocation.

void __test (int &t)
{
}

template<typename t>
void Test (const t &t)
{
  //do other 
  things ... __test (t);
}

int i = 0;
Test (i);

The above code will fail to compile, for the reason that T is a const t& after entering test, and the __test is t& after the call, and the template cannot be deduced from the const t& to t&. There are two ways to solve this problem:

Template<typename t>
void Test (const t& T)
{
  //do other things ...
  __test (const_cast<t&> (T));

After modification, test can be compiled through. We removed the const attribute by const_cast, think about it, and use const_cast to fit it. It is obvious that the const_cast destroys the robustness of the function. There is no other way to solve it. Our Special Template table solves this problem with the appropriate type or overload invocation function:

void __test (const int &t)
{}

//or
void __test (int & T)
{}
//or
Template<typename t>
void Test (t &t)
{}

//or
template<typename t>
void Test (t &t)
{}

This problem is resolved smoothly, whether we call Test (i) or test (5) can be successfully compiled.

The real world code is not so simple, the test code above only involves a parameter, if a template function has two parameters encountered this situation, what to do. C++0x/98 we might be able to write this code:

Template<typename t>
void Test (t &t1, T &t2)
{
  __test (t1, T2);
}
Template<typename t>
void Test (const t &T1, const T &t2)
{
  __test (t1, T2);
}
Template<typename t>
void Test (const t &T1, T &t2)
{
  __test (t1, T2);
}
Template<typename t>
void Test (t &t1, const T &t2)
{
  __test (t1, T2);
}

When I finished this sample code, my first feeling was that it was a bit of a bit of a bit of technology, and that programmers couldn't waste time on this piece of code. When we have two parameters, the template function will have 4 special features. The problem is not here, because the parameter types are different, and the test eventually forwards the arguments to the __test, which means something. means that we also overload versions of different parameter types for __test. Two parameters can be overloaded out of 4, if there are three parameters, four parameters, or even five parameters, __test exactly how many overloaded functions to meet. This is why the 5 parameters of VC9 's std::bind overload 63 functions, how large the number, and these are all built on human resources.

Either reduce the number of template function test, or reduce the number of __test, the programmer should use the brain and hand where needed.

Now that we have the value of the left and the right is worth the concept, this problem has a better solution.

Template<typename t>
struct removereference
{
  typedef T Type;
};

Template<typename t>
struct removereference<t&>
{
  typedef T Type;
};

Template<typename t>
struct removereference<t&&>
{
  typedef T Type;
};

Template<typename t>
t&& forwardvalue (typename removereference<t>::type&& value)
{return
  (t&&) value;
}

Template<typename t>
t&& forwardvalue (typename removereference<t>::type& value)
{return
  (t&&) value;
}

With the above code (which is actually the general implementation of Std::forward), our code is much simpler:

Template<typename t>
void Test (t &&t1, T &&t2)
{
  __test forwardvalue<t> ( T1), forwardvalue<t> (T2));

No matter what form of arguments our __test accepts, even if there are multiple forms of __test, it can be invoked into the correct __test, thus eliminating the need for more than one template function, which is perfect forwarding.

Perfect forwarding: Whatever type of argument is required for the purpose call function can be correctly invoked to the function we want. There is a && in the example code on Main, which is called the right value reference for the &&. The realization of perfect forwarding in c++11 is dependent on type derivation and reference folding. Type deduction Needless to say, the container in the STL widely uses the type derivation, that refers to the folding type what.

The reference collapse rule is a reference simplification between the function acceptance parameter form and the incoming argument form, and the specific compiler defines such a rule: t& + & => t& t&& + & => t& t& + ;& => t& t&& + && => t&&

In the above rule, the former represents the type of acceptance, which represents the type,=> after the reference is folded, that is, the type of the last deduced decision.

Now let's take a sample code to see how the reference folding works.

Template <typename t> struct Name;
  Template <> struct name<string> {static const char * get () {return "string";

}
};
  Template <> struct Name<const string> {static const char * get () {return "const string";

}
};
  Template <> struct name<string&> {static const char * get () {return "string&";

}
}; Template <> struct Name<const string&> {static const char * get () {return "const string&"
  ;

}
};
  Template <> struct name<string&&> {static const char * get () {return "string&&";

}
}; Template <> struct Name<const string&&> {static const char * get () {return "Const STRING&A
  mp;& ";

}
}; Template <typename t> void quark (t&& T) {cout << "**********************************" << en
  dl
  cout << "T:" << t << Endl; cout << "T:" &LT;&LT  Name<t>::get () << Endl;  -->a cout << "t&&:" << name<t&&>::get () << Endl;
-->b cout << Endl;

String Strange () {return "strange ()";}


const string Charm () {return "charm ()";}
  int main () {string up (' up ');

  Const string Down ("down");    Quark (UP);  -->1 Quark (down); -->2 Quark (Strange ());       -->3 Quark (Charm ());
-->4 return 0; }

From the above instance code, you can generally know the operation of the reference folding. The above code runs the result:

T:up
t:string&
T&&: string&

T:down
T:const string&
T&&: const string&

T:strange ()
T:string
T&&: string&&

T:charm ()
T:const string
T&&: const string&&
When calling quack, the up is deduced to be a string& type. In Quack, T is deduced to be a string& type, according to the t& + &&-> t& rule, that is, to enter quack, T is String&, so a invokes the string& version. is still the rule above, in the execution of B, T is still converted and deduced as String&, so it goes to the appropriate version.
Because down has a const property, the expression is the same as 1, the only difference being that both A and B are called to a version that has a const attribute.

This is a bit special because strange () returns a temporary object with a type string, so after entering quack, T is still a string, so a will eventually go into the calling version of String, and t&& is string& And will go to the corresponding version.
This is a bit special because charm () returns a temporary object with a type string, so after entering quack, T is a string, and because of the Const property, A and B call the version of the Const property.
In the above code we give the general implementation of the Std::forward, in fact, is to use the reference to collapse rules to retain the original type of argument, the compiler to reject the type deduction, in order to achieve the perfect parameter forwarding to the destination function.

All the existence of the perfect forwarding, the essence of the problem is: template parameter type deduction in the forwarding process can not guarantee the reference problem of the left and right values. The perfect forwarding is to solve this problem by increasing the reference concept of the left and right values and the new parameter derivation rules without destroying the const attribute. std::forward Keywords: solving

In the template, if we need to pass a set of parameters intact to another parameter, in the absence of perfect forwarding, consider the parameters will have multiple types of overloads, so in the absence of perfect forwarding, the number of overloaded functions will reach 2^n, how large the amount of labor. When using Std::forward as a template parameter to derive the rule to keep the parameter attributes unchanged, the realization of perfect forwarding saves a lot of work.

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.