深入解读C++中的右值引用

2020-01-06 15:15:48刘景俊
如上代码中,发生了引用折叠,将TR展开,得到 T& & v = 1(注意这里不是右值引用)。 这里的 T& + & 被折叠为 T&。更为详细的,根据TR的类型定义,以及v的声明,发生的折叠规则如下:

T& + &  = T&
T& + && = T&
T&& + &  = T&
T&& + && = T&&

上面的规则被简化为:只要出现左值引用,规则总是优先折叠为左值引用。仅当出现两个右值引用才会折叠为右值引用。
再谈转发
那么上面的引用折叠规则,对完美转发有什么用呢?我们注意到,对于T&&类型,它和左值引用折叠为左值引用,和右值引用折叠为右值引用。基于这种特性,我们可以用 T&& 作为我们的转发函数模板参数:

template<typename T>
void Forward(T&& t)
{
 Do(static_cast<T&&>(t));
}

这样,无论Forward接收到的是左值,右值,常量,非常量,t都能保持为其正确类型。
当传入左值引用 X& 时:

void Forward(X& && t)
{
 Do(static_cast<X& &&>(t));
}

折叠后:

void Forward(X& t)
{
 Do(static_cast<X&>(t));
}

这里的static_cast看起来似乎是没有必要,而它实际上是为右值引用准备的:

void Forward(X&& && t)
{
 Do(static_cast<X&& &&>(t));
}

折叠后:

void Forward(X&& t)
{
 Do(static_cast<X&&>(t));
}

前面提到过,可以接收右值的右值引用本身却是个左值,因为它具名并且可以取值。因此在Forward(X&& t)中,参数t已经是一个左值了,此时我们需要将其转换为它本身传入的类型,即为右值。由于static_cast中引用折叠的存在,我们总能还原参数本来的类型。
在C++11中,static_cast<T&&>(t) 可以通过 std::forward<T>(t) 来替代,std::forward是C++11用于实现完美转发的一个函数,它和std::move一样,都通过static_cast来实现。我们的Forward函数最终变成了:

template<typename T>
void Forward(T&& t)
{
 Do(std::forward<T>(t));
}

可以通过如下代码来测试:

#include<iostream>
using namespace std;

void Do(int& i)    { cout << "左值引用"  << endl; }
void Do(int&& i)   { cout << "右值引用"  << endl; }
void Do(const int& i) { cout << "常量左值引用" << endl; }
void Do(const int&& i) { cout << "常量右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t){ Do(forward<T>(t)); }

int main()
{
 int a;
 const int b;
 
 PerfectForward(a);  // 左值引用
 PerfectForward(move(a)); // 右值引用
 PerfectForward(b);  // 常量左值引用
 PerfectForward(move(b)); // 常量右值引用
 return 0;
}