C++完美转发详解
我们知道一个已经定义的右值引用其实是一个左值,这样在参数转发(传递)时就会产生一些问题。
例如,在函数的嵌套调用时,外层函数接收一个右值作为参数,但外层函数将参数转发给内层函数时,参数就变成了一个左值,并不是它原来的类型了。
针对这种情况,C++11 标准提供了一个函数
例如,将本例中的第 10 行代码修改为下列形式:
引用折叠规则如表 1 所示。
根据表 1 可推导出内层函数最终接收到的参数是左值引用还是右值引用。
在引用折叠规则中:
在 C++11 标准库中,完美转发的应用非常广泛,如一些简单好用的函数(如
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
例如,在函数的嵌套调用时,外层函数接收一个右值作为参数,但外层函数将参数转发给内层函数时,参数就变成了一个左值,并不是它原来的类型了。
示例
下面通过案例演示参数转发的问题,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; }运行结果:
左值
右值
- 第 3~6 行代码定义了两个重载的模板函数 transimit(),第一个重载函数接收一个左值引用作为参数,第二个重载函数接收一个右值引用作为参数;
- 第 7~12 行代码定义模板函数 test(),在 test() 函数内部以不同的参数调用 transimit() 函数;
- 第 15 行代码调用 test() 函数,传入右值 1 作为参数。
由运行结果可知,使用右值 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 所示。
形参类型 | 实参类型 | 实际接收的类型 |
---|---|---|
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()
等)都使用了完美转发,它们减少了函数版本的重复,并且充分利用了右值引用,既简化了代码量,又提高了程序的运行效率。声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。