C++中的完美转发

C++中的完美转发(perfect-forwarding)到底是什么?说到底,它其实就是一个类型转换,能够将传递到母函数的参数原封不动(这里的原封不动不仅指值不变,还包括类型信息,限定符之类的)在转发给其他函数。
表示形式就是这样:

func(expr);  //处理函数

fwd(expr)
{
    func(std::forward(expr)); //这里省略了很多细节
}

fwd就是上面我们说的母函数,我们调用fwd时传递进去的参数,如何原封不动的传递给其他函数,那就是完美转发要做的事。

下面先看一下C++中的关于它的源码实现:

//type_traits文件

    // TEMPLATE FUNCTION forward
template<class _Ty> inline
    constexpr _Ty&& forward(
        typename remove_reference<_Ty>::type& _Arg) _NOEXCEPT
    {   // forward an lvalue as either an lvalue or an rvalue
    return (static_cast<_Ty&&>(_Arg));
    }

template<class _Ty> inline
    constexpr _Ty&& forward(
        typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
    {   // forward an rvalue as an rvalue
    static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
    return (static_cast<_Ty&&>(_Arg));
    }

这里我简化一下,让你们看的更加明白:

    // TEMPLATE FUNCTION forward
template<typename T> 
 T&& forward(typename remove_reference::type& _Arg) 
{   
    // forward an lvalue as either an lvalue or an rvalue
    return (static_cast(_Arg));
}

template<typename T> 
T&& forward(typename remove_reference::type&& _Arg) 
{   
    // forward an rvalue as an rvalue
    return (static_cast(_Arg));
}

去掉了很多优化(如constexpr,noexcept等)和一些静态断言,另外typename可以完全替代class,然后下面开始分析:
首先,可以看出来,forward函数对于参数是左值和右值都实现了一个版本,上面的接收左值,下面的接收右值参数。remove_reference::type就是将T的引用全部去除,比如T是int&,就变成了int。然后后面加上一个&就是左值引用,加上&&就是右值引用。
然后就是执行以下类型转换。这里的T&&其实是一个universal引用,关于universal引用,可以参考这篇文章,这里就不再赘述。
理解了universal引用,上面的源代码就极其容易理解了。其实,上面的两个版本我觉得可以用一个就实现了:

template<typename T> 
 T&& forward(T&& _Arg) 
{   
    return (static_cast(_Arg));
}

既然知道了完美转发的原理,那么我们其实可以用另外一种方式来表达完美转发,考虑下面的例子:

#include 

void test(int& a){
    std::cout<<"lvalue"<<std::endl;
}

void test(int&& a){
    std::cout<<"rvalue"<<std::endl;
}

template<typename T>
void fwd(T&& a){
    test(std::forward(a));
}

int main()
{   
    int a;
    fwd(a);            //打印 lvalue
    fwd(std::move(a)); //打印 rvalue
    cout << endl << endl;
    return 0;
}

当fwd函数传递的是一个左值,就打印lvalue,右值则打印rvalue,很好理解。那么换一种实现:

#include 

void test(int& a){
    std::cout<<"lvalue"<<std::endl;
}

void test(int&& a){
    std::cout<<"rvalue"<<std::endl;
}

template<typename T>
void fwd(T&& a){
    test(static_cast<decltype(a)>(a));
}
int main()
{   
    int a;
    fwd(a);            //打印 lvalue
    fwd(std::move(a)); //打印 rvalue
    cout << endl << endl;
    return 0;
}

运行结果和上面一模一样。因为decltype(a)会得出a的真正类型,所以就将它转换成它真正的类型。
上面就等价于:

#include 

void test(int& a){
    std::cout<<"lvalue"<<std::endl;
}

void test(int&& a){
    std::cout<<"rvalue"<<std::endl;
}

template<typename T>
void fwd(T&& a){
    test(static_cast(a));
}
int main()
{   
    int a;
    fwd(a);            //打印 lvalue
    fwd(std::move(a)); //打印 rvalue
    cout << endl << endl;
    return 0;
}

a的类型就是T&&(这里是一个universal引用类型),所以decltype(a)就是T&&。
但是如果修改成下面这样:

#include 

void test(int& a){
    std::cout<<"lvalue"<<std::endl;
}

void test(int&& a){
    std::cout<<"rvalue"<<std::endl;
}

template<typename T>
void fwd(T&& a){
    test(static_cast(a));
}
int main()
{   
    int a;
    fwd(a);            //打印 lvalue
    fwd(std::move(a)); //打印 lvalue
    cout << endl << endl;
    return 0;
}

就只能打印lvalue了。为什么?考虑两种情况:

  • 当调用fwd(a)时,传递一个左值,test模板的T类型推断成int&,所以static_cast(a)就是将a转换成一个左值引用,调用test的左值版本,没问题
  • 当调用fwd(std::move(a))时,传递一个右值,test模板的T类型推断成int,static_cast(a)就是将a转换成int类型,还是一个左值,所以调用test的左值版本。

另外,记住我们写代码有时候可能有多种表达方式,但是我们应该选择一种更直观的方式去表达。比如std::forward可以完全替代std::move(),比如上面的:

fwd(std::move(a));  

可以写成:

fwd(std::forward(a));

但是这样代码就没有那么直观了,意图不明显,比较难读懂。
所以,即使你知道可以使用类型转换实现完美转发,但是还是尽量用std::forward函数吧,因为这样意图很明显,很容易就知道你想表达什么。

你可能感兴趣的:(c++)