Chapter 6 Mobile semantics and enable_if: 6.1 perfect forwarding

Chapter 6: Move Semantics and enable_if<>

Chapter 6 mobile semantics and enable_if <>

One of the most prominent features C++11 introduced was move semantics. You can use it to optimize copying and assignments by moving (“stealing”) internal resources from a source object to a destination object instead of copying those contents. This can be done provided the source no longer needs its internal value or state (because it is about to be discarded).

Mobile semantics is one of the most prominent features introduced in C ++ 11. Move semantics can optimize copy and assignment operations: move ("steal") the internal resources of the source object to the target object instead of copying their contents. The premise of this is that the source object no longer needs its internal value or state (because the source object will be discarded).

 

Move semantics has a significant influence on the design of templates, and special rules were introduced to support move semantics in generic code. This chapter introduces these features.

Mobile semantics has an important influence on the design of templates, and some special rules have been introduced in generic code to support mobile semantics. This chapter introduces these features.

 

6.1 Perfect Forwarding

6.1 Perfect forwarding

 

Suppose you want to write generic code that forwards the basic property of passed arguments:

If you want to write generic code to forward the basic attributes of incoming parameters:

  • Modifyable objects should be forwarded so that they still can be modified.

   Modifiable objects are still modifiable after being forwarded.

  • Constant objects should be forwarded as read-only objects.

    const objects can only be forwarded as read-only objects.

  • Movable objects (objects we can “steal” from because they are about to expire) should be forwarded as movable objects.

    Removable objects (objects from which resources can be stolen because they are about to expire) are still removable after being forwarded.

To achieve this functionality without templates, we have to program all three cases.

Without using templates, in order to achieve this goal, you need to program the above three situations separately.

For example, to forward a call of f() to a corresponding function g():

For example, to forward the parameters passed when calling f () to the corresponding g () function:

 

#include <utility>
#include <iostream>class X {
    ...
};void g(X&) {
    std::cout << "g() for variable\n";
}void g(X const&) {
    std::cout << "g() for constant\n";
}void g(X&&) {
    std::cout << "g() for movable object\n";
}// let f() forward argument val to g():void f(X&val) {











    g(val); // val is non-const lvalue => calls g(X&)
}

void f(X const& val) {
    g(val); // val is const lvalue => calls g(X const&)
}

void f(X&& val) {
    g(std::move(val)); // val is non-const lvalue => needs std::move() to call g(X&&)
}

int main()
{
    X v; // create variable
    X const c; // create constant

    f(v); // f() for nonconstant object calls f(X&) => calls g(X&)
    f(c); // f() for constant object calls f(X const&) => calls g(X const&)
    f(X()); // f() for temporary calls f(X&&) => calls g(X&&)
    f(std::move(v)); // f() for movable variable calls f(X&&) => calls g(X&&)
}

Here, we see three different implementations of f() forwarding its argument to g():

Three different f () functions are defined here, and they will forward their parameters to the g () function:

void f (X & val) { 
    g (val); // val is a non-const lvalue => calls g (X &) 
} 

void f (X const & val) { 
    g (val); // val is a const Lvalue => calls g (X const &) 
} 

void f (X && val) { 
    g (std :: move (val)); // val is a non-const lvalue => need to use std :: move g (X &&) 
}

Note that the code for movable objects (via an rvalue reference) differs from the other code: It needs a std::move() because according to language rules, move semantics is not passed through. Although val in the third f() is declared as rvalue reference its value category when used as expression is a nonconstant lvalue (see Appendix B) and behaves as val in the first f(). Without the move(), g(X&) for nonconstant lvalues instead of g(&&) would be called.

Note that the code for movable objects (referenced by rvalues) is different from other codes: it needs to call std :: move (), because according to language rules, move semantics are not passed. Although in the third f () function, val is declared as an rvalue reference, when used as an expression, its value type is a non-const lvalue (see Appendix B), and its behavior is the same as the first The f () function is the same. Therefore, if you do not use move (), the non-const lvalue version of g (X &) will be called instead of the g (&&) function.

 

If we want to combine all three cases in generic code, we have a problem:

If we want to unify the above three situations in generic code, we will encounter a problem:

template<typename T>
void f(T val) {
    g(val);
}

works for the first two cases, but not for the (third) case where movable objects are passed.

This template is only valid for the first two cases, but it is invalid for the third movable object.

 

C++11 for this reason introduces special rules for perfect forwarding parameters. The idiomatic code pattern to achieve this is as follows:

Therefore, C ++ 11 introduces special rules to perfectly forward the parameters. The idiomatic code pattern to achieve this goal is as follows:

template <typename T>
 void f (T && val) { 
    g (std :: forward <T> (val)); // Fully forward val to g () 
}

Note that std::move() has no template parameter and “triggers” move semantics for the passed argument, while std::forward<>() “forwards” potential move semantic depending on a passed template argument.

Note: std :: move () has no template parameters, it will move its parameters unconditionally. And std :: forward <> will decide whether to "forward" its potential mobile semantics according to the specific conditions of the passed parameters.

 

Don’t assume that T&& for a template parameter T behaves as X&& for a specific type X. Different rules apply! However, syntactically they look identical:

Do not assume that the T && of the template parameter T is the same as the X && of the specific type X. Although the syntax looks the same, they apply to different rules:

  • X&& for a specific type X declares a parameter to be an rvalue reference. It can only be bound to a movable object (a prvalue, such as a temporary object, and an xvalue, such as an object passed with std::move(); see Appendix B for details). It is always mutable and you can always “steal” its value.

  X && of the specific type X declares an rvalue reference. Can only be bound to a moving object (prvalue, such as a temporary object. Xvalue, such as an object passed through std :: move (). See Appendix B for more details). Its value is variable, but can always be "stolen".

  • T&& for a template parameter T declares a forwarding reference (also called universal reference).  It can be bound to a mutable, immutable (i.e., const), or movable object. Inside the function definition, the parameter may be mutable,immutable, or refer to a value you can “steal” the internals from.

  The T && of the template parameter T declares a forward reference (also called a universal reference). Can be bound to mutable, immutable (such as const) or movable objects. In the definition inside the function, this parameter is also variable, immutable, or points to a value that can be "stealed" of internal data.

 

Note that T must really be the name of a template parameter. Depending on a template parameter is not sufficient. For a template parameter T, a declaration such as typename T::iterator&& is just an rvalue reference, not a forwarding reference.

Note that T must be the name of the template parameter. Just relying on template parameters is not enough. For example, for the template parameter T, typname T :: iterator && only declares an rvalue reference, not a forward reference.

So, the whole program to perfect forward arguments will look as follows:

Therefore, the entire program for perfectly forwarding parameters will look like this:

include <utility>
#include <iostream>

class X {
    ...
};

void g(X&) {
    std::cout << "g() for variable\n";
}

void g(X const&) {
    std::cout << "g() for constant\n";
}

void g(X&&) {
    std::cout << "g() for movable object\n";
}

// let f() perfect forward argument val to g():
template<typename T>
void f(T&& val) {
    g(std::forward<T>(val)); // 对于任何val参数都可调用正确的 g() 函数。
}

int main()
{
    X v; // create variable
    X const c; // create constant

    f(v); // f() for variable calls f(X&) => calls g(X&)
    f(c); // f() for constant calls f(X const&) => calls g(X const&)
    f(X()); // f() for temporary calls f(X&&) => calls g(X&&)

    f(std::move(v)); // f() for move-enabled variable calls f(X&&)=> calls g(X&&)
}

Of course, perfect forwarding can also be used with variadic templates (see Section 4.3 on page 60 for some examples). See Section 15.6.3 on page 280 for details of perfect forwarding.

Of course, perfect forwarding can also be applied to variable templates (see Section 4.3 on page 60 for more examples). For more details about perfect forwarding, please refer to section 15.6.3 on page 280.

Guess you like

Origin www.cnblogs.com/5iedu/p/12761344.html