C++ 完美转发之 std::forward
2025-03-31

为什么需要完美转发?

在 C++11 之前,泛型函数在传递参数时无法保持参数的原始类型(左值或右值),导致额外的拷贝或移动操作。完美转发是一种高效传递参数的技术,能够保持参数的原始特性(左值传递左值,右值传递右值),避免额外的开销。

代码例子

注意,万能引用 = 右值引用 + 模板。如果没有模板,就是普通的右值引用。

未使用完美转发

下面的代码中,我们没有使用完美转发,而是直接将参数传递到了 process 函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

void process(int& x) {
cout << "Lvalue reference: " << x << endl;
}

void process(int&& x) {
cout << "Rvalue reference: " << x << endl;
}

template<typename T>
void forwardExample(T&& arg) {
process(arg); // 注意:未使用完美转发
}

int main() {
int a = 10;
forwardExample(a); // 传递左值 → 期望调用左值版本 process(int&)
forwardExample(20); // 传递右值 → 期望调用右值版本 process(int&&)
return 0;
}

调用的结果,均调用了左值引用:

1
2
Lvalue reference: 10
Lvalue reference: 20

我们明明传的是右值,结果两个函数间接调用 process 时最终都调用了左值的版本。这是因为 forwardExample(T&& arg) 到这一层时,argprocess 看来已经是一个有地址的变量,将其看成了左值。

使用完美转发

下面的代码中,我们使用了完美转发。

1
2
3
4
template<typename T>
void forwardExample(T&& arg) {
process(std::forward<T>(arg)); // 关键:std::forward 保持原始值类别
}

调用的结果完美符合了我们的期望,左值调用左值重载,右值调用右值重载:

1
2
Lvalue reference: 10
Rvalue reference: 20

完美转发的实现原理

std::forward<T>(arg) 通过引用折叠和类型推导来保证传递原始的参数特性。

T 传递的类型 T&& 推导后 std::forward<T>(arg) 结果
int int&& 右值 int&&
int& int& &&int& 左值 int&
int&& int&& &&int&& 右值 int&&

实际上就是左值引用会污染,万能引用沾染上一个左值引用就变成左值引用了。

完美转发的应用场景

  1. 传递构造函数参数:有可能很多类的构造函数有左值右值重载,std::forward 可以确保 arg 以最佳方式传递给构造函数。
  2. 传递函数参数:文中的例子。