C++11 转发和完美转发

基本概念

函数将一个或多个实参传递给其他函数,这个过程称为转发。
完美转发(perfect forwarding)是指转发过程中,保持被转发实参的所有原始性质,包括实参类型是否为const,左值 or 右值等。

转发

转发示例

编写一个转发的示例:翻转函数flip1能将2个参数逆序,传递给函数f。

// 转发示例
void f(int v1, int v2);

template<typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2) { // 翻转函数:翻转参数t1、t2顺序
	f(t2, t1);
}

一般情况下,flip1能工作得很好。但当我们希望它调用一个接受引用参数的函数时,会出现问题:

void f(int v1, int& v2) { // v2是一个引用类型
	cout << v1 << " " << ++v2 << endl;
}

代码中,f改变了绑定到v2的实参值,但并不会影响flip1的实参。因为实参传递给flip1的形参,只是一个普通的int,而非引用(int&),所以改变flip1的形参并不会影响其实参。

定义能保持类型信息的函数参数

为了让翻转函数传递一个引用,可以重写翻转函数,使其参数能保持给定实参的“左值性”,以及const属性(常量性)。

template<typename F, typename T1, typename T2>
void flip2(F f, T1&& t1, T2&& t2) {
	f(t2, t1);
}

如果t1或t2对应实参是左值(int&),通过引用折叠,转换成int&,继续保持左值属性;
如果实参是右值(int&&),同样地,转换成int&&,继续保持右值属性。

这个版本的翻转函数flip2只解决了一半问题,对于接受一个左值引用的函数工作正常,但是对于接受右值引用的函数,却无法正常工作。
如,通过flip2调用g,参数

// 接受一个右值引用和一个左值引用的函数
void g(int&& i, int& j) {
	cout << i << " " << j << endl;
}

flip2(g, i, 42); // 错误:不能从一个左值实例化int&&

虽然flip2的实参42是右值,但在flip2内部调用g,传给g的实参将是flip2的参数t2(值为42),而函数参数t2与其他任何变量一样,都是左值表达式。将左值实参直接传递给右值形参,将导致编译错误。

完美转发std::forward

C++11中,可以使用std::forward传递flip2的参数,使它能保持原始实参的类型的所有细节。
头文件:

与move调用不同的是,forward必须通过显式模板实参来调用。forward返回该显式实参类型的右值引用,i.e. forward返回类型为T&&。

使用forward重写翻转函数:

// OK
template<typename F, typename T1, typename T2>
void flip3(F f, T1&& t1, T2&& t2) {
	f(std::forward<T2>(t2), std::forward<T1>(t1));
}

// 像这样调用不再有问题
int i = 10, j = 20;
flip2(g, i, 20); // OK

参考

[1]斯坦利·李普曼, 约瑟·拉乔伊, 芭芭拉·默,等. C++ Primer中文版(第5版)[J]. 中国科技信息, 2013.

posted @ 2022-01-19 00:22  明明1109  阅读(675)  评论(0编辑  收藏  举报