A brief analysis of perfect forwarding

 Therefore, the keyword we need to grasp is forwarding, which means forwarding one or more actual parameters to other functions unchanged . The objects we need to grasp are other functions, and the keyword we need to grasp is actual parameters. And we also need to keep in mind that perfect forwarding is a concept in template programming and is generally used in template programming!

Why do you say unchanged? Could it be that without perfect forwarding, the actual parameters will change when forwarding them to other functions? What will change? Below, we will give an example to point out the problems in some practices that prevent us from achieving perfect forwarding. Using std::forward can help us achieve perfect forwarding!

Let's take a look at the following code:

#include<iostream>

void func1(int&& arg1)
{
    func2(arg1);
}

void func2(int&& arg2);

The above code conforms to the scenario we mentioned above. We want to forward the actual parameters in the func1 function to func2.

But the above code actually fails to compile. Take a look at the error message:

 If you are not very clear about the conversion between lvalue references and rvalue references, you may be confused. arg1 is not of rvalue reference type, and the parameters accepted by func2 are also of rvalue reference type!

However, function parameters, like any other variables, are lvalue expressions (or move semantics are not transferable! This design is intentional, because if move semantics are transferable, you will lost its value)! So you cannot pass an lvalue reference to an rvalue so an error is reported! So here our actual parameters have changed , from rvalue references to lvalue references!

Next we use the forward function to avoid this problem. We simply modify the code:

#include<iostream>

void func1(int&& arg1)
{
    func2(std::forward<int&&>(arg1));
}

void func2(int&& arg2);

This time the compilation passed! By adding forward to arg1, we can transform it from an lvalue reference back to an rvalue reference! This achieves the perfect forwarding we mentioned above! That is to say: if the parameters of our wrapper function are passed as lvalues, then what we get when the internal function is called is an lvalue. If the parameters of our wrapper function are passed as rvalues, then what we get when the internal functions are called is rvalues. .

Regarding the specific principle of the forward function, it is actually similar to the move function. It uses perfect references and reference folding, so that no matter whether it is an lvalue reference or an rvalue reference, the final result is the type specified by the template type! (So ​​it is the same as using move instead of forward here, because our forward rvalue and move have the same effect)

In fact, the scenario of forwarding the parameters of one function to another function as we mentioned above is quite common, such as the following factory pattern code (students who are slightly familiar with design patterns should understand it)

 Next, we will use the above function to demonstrate perfect forwarding in template programming!

The above function is not perfect now, because its function parameters are only formal parameters, and copy construction will be performed when the function is called, so we can add reference symbols, so that the performance will be improved.

#include<iostream>
#include <memory>
using namespace std;


template<typename T, typename Arg>
shared_ptr<T> factory(Arg& arg)
{
    return shared_ptr<T>(new T(arg));
}

But there is still a problem. The above code cannot pass rvalues, so we can overload a const reference (const references can accept rvalues)

#include<iostream>
#include <memory>
using namespace std;


template<typename T, typename Arg>
shared_ptr<T> factory(Arg& arg)
{
    return shared_ptr<T>(new T(arg));
}

template<typename T, typename Arg>
shared_ptr<T> factory(Arg const& arg)
{
    return shared_ptr<T>(new T(arg));
}

But there are still problems. There is only one parameter here. If there are multiple parameters, and if some parameters require lvalues ​​and some require rvalues, then you have to write multiple overloaded functions (permutations and combinations), which is really not easy. good.

Next, we will use the perfect forwarding mentioned above to achieve perfect forwarding in template programming:

#include<iostream>
#include <memory>
using namespace std;


template<typename T, typename Arg>
shared_ptr<T> factory(Arg&& arg)
{
    return shared_ptr<T>(new T(std::forward<Arg>(arg)));
}

First, our function parameter Arg&& arg uses reference folding so that it can match any lvalue or rvalue, thus solving the overloading problem mentioned above. Secondly, we used std::forward to solve the parameter forwarding problem mentioned above! In this way, perfect forwarding is achieved!

Let us verify it below:

#include<iostream>
#include <memory>
using namespace std;


class CTemp
{
    int m_iData;
public:
    CTemp(int& arg):m_iData(arg)
    {
        cout << "Ctemp(int& arg)" << endl;
    }
    CTemp(int&& arg):m_iData(arg)
    {
        cout << "Ctemp(int&& arg)" << endl;
    }
};


template<typename T, typename Arg>
shared_ptr<T> factory(Arg&& arg)
{
    return shared_ptr<T>(new T(std::forward<Arg>(arg)));
}

int main(int argc, char* argv[])
{
    int value = 5;
    auto p1 = factory<CTemp>(5);
    auto p2 = factory<CTemp>(value);
    return 0;
}

operation result:

So, you can see that we can match any lvalue or rvalue after using perfect forwarding!

References:

C++ Primer 5th Edition

C++ New Standard 002_You can achieve "perfect forwarding" with your little fingers_bilibili_bilibili

Guess you like

Origin blog.csdn.net/qq_55621259/article/details/129969430
Recommended