C++完美转发为什么必须要有std::forward?

先看一种情况,它的输出结果是什么?

#include <iostream>
using namespace std;

void F(const int &a)
{
    cout << "int: " << a << endl;
}

void F(int &&a)
{
    cout << "int &&: " << a << endl;
}

template <typename T>
void G(T &&t)
{
    return F(t); // 1  direct call
    // return F(std::forward<T>(t)); // 2  forward call
}

int main()
{
    int i = 1;
    G(i);
    G(2);

    return 0;
}

Output:

int: 1
int: 2

这就不符合我们的预期呀,我们希望G(i)调用左值函数,G(2)调用右值函数,而实际上两者都调用的左值函数。因此,需要std::forward

如果我们采用第2种,输出是什么呢?

return F(std::forward<T>(t)); // 2  forward call

Output:

int: 1
int &&: 2

OK, nice

为什么会这样呢,根本原因在于右值引用指向右值,但本身是左值,也就是说T&& t中,t指向右值,但t本身是左值。
所以,如果不采用发转,不管传进来的是右值,经过右值引用也会变成左值,从而去调用左值函数

用forward(t)当实参,T推断成int&&,所以返回值是int&& &&,折叠成int&&,可以被绑定到F参数的右值引用上,就没有错误了

所以,universal reference 转发时并不完美,只完美了一半,当转发目标的的参数是右值引用时,会出现问题。
std::forward 解决当转发目标的的参数是右值引用时的问题。可以保持原始参数的类型,将实参从原来的类型为右值引用的左值,变成了本身就是右值引用

std::forward做了什么,为什么可以保持类型不变?

C++11为什么要引入 右值引用 这个概念? 它与左值引用有什么本质区别?实际上二者在编译后的汇编层面,就是变量的地址,没有任何区别。那么为什么“画蛇添足”增加个 右值引用 概念?因为要在编译时告诉编译器,这里传入的 “变量的地址”,是个普通变量还是濒死的除了此处不再使用的变量。如果是后者,可以用move语义,直接把它的内容、资源给搬走,不用“深拷贝”了。所以题主的问题,对于函数模板 void G(A &&a),它的内部怎么知道形参对应的实参,是个普通变量,还是濒死的临时变量? 答案是,显然、当然没法知道,只能原封不动的完美的转发给函数模板 void G(A &&a)调用的下一层函数。这就是std::forward的用途。如果你用Visual C++,你可以直接搜一下它所带的STL源代码中std::forward的实现。实际上std::forward啥都不做,就是取变量的地址而已。

posted @ 2023-09-14 23:21  Rogn  阅读(271)  评论(1编辑  收藏  举报