首页 > 编程笔记

C++完美转发详解

我们知道一个已经定义的右值引用其实是一个左值,这样在参数转发(传递)时就会产生一些问题。

例如,在函数的嵌套调用时,外层函数接收一个右值作为参数,但外层函数将参数转发给内层函数时,参数就变成了一个左值,并不是它原来的类型了。

示例

下面通过案例演示参数转发的问题,C++ 代码如下:
#include<iostream>
using namespace std;
template<typename T>
void transimit(T& t) { cout << "左值" << endl; }
template<typename T>
void transimit(T&& t) { cout << "右值" << endl; }
template<typename U>
void test(U&& u)
{
    transimit(u);   //调用transimit()函数
    transimit(move(u));   //调用transimit()函数
}
int main()
{
    test(1);   //调用test()函数
    return 0;
}
运行结果:

左值
右值

示例分析:

由运行结果可知,使用右值 1 调用 test() 函数时,test() 函数的输出结果是“左值”“右值”。在调用过程中,右值 1 到 test() 函数内部变成了左值,因此 transimit(u) 其实是接收的左值,输出了“左值”;第二次调用 transimit() 函数时,使用 move() 函数将左值转换为右值,因此 transimit(move(u)) 输出结果为“右值”。

本例调用 test() 函数时,传递的是右值,但在 test() 函数内部,第一次调用 transimit() 函数时,右值变为左值,这显然不符合程序设计者的期望。

针对这种情况,C++11 标准提供了一个函数forward(),它能够完全依照模板的参数类型,将参数传递给函数模板中调用的函数,即参数在转发过程中,参数类型一直保持不变,这种转发方式称为完美转发。

例如,将本例中的第 10 行代码修改为下列形式:
transimit(forward<U>(u));   //调用forward()函数实现完美转发
此时,再调用 test(1) 函数时,其输出结果均为“右值”。forward() 函数在实现完美转发时遵循引用折叠规则,该规则通过形参和实参的类型推导出内层函数接收到的参数的实际类型。

引用折叠规则如表 1 所示。

表1 引用折叠规则实参类型
形参类型 实参类型 实际接收的类型
T& T T&
T& T& T&
T& T&& T&
T&& T T&&
T&& T& T&
T&& T&& T&&

根据表 1 可推导出内层函数最终接收到的参数是左值引用还是右值引用。

在引用折叠规则中:
在 C++11 标准库中,完美转发的应用非常广泛,如一些简单好用的函数(如make_pair()make_unique()等)都使用了完美转发,它们减少了函数版本的重复,并且充分利用了右值引用,既简化了代码量,又提高了程序的运行效率。

优秀文章