完美转发(std::forward)

完美转发(std::forward)

完美转发的目标可以用一句话概括:把收到的参数,尽可能原样地转交给下一个函数。
它是模板代码里非常常见的一项能力,核心工具就是 std::forward

1. 为什么需要它?

假设我们写一个中转函数:

1
2
3
4
5
6
7
8
9
void target(const std::string& s)
{
}

template <typename T>
void wrapper(T&& arg)
{
target(arg);
}

看起来没问题,但这里的 arg 在函数体里其实是一个左值。
也就是说,原本传进来如果是右值,中间这层包装后,右值语义可能就丢了。

2. std::forward 的作用

正确写法通常是:

1
2
3
4
5
template <typename T>
void wrapper(T&& arg)
{
target(std::forward<T>(arg));
}

这样 arg 的值类别就能尽量保持不变。

3. 一个更直观的例子

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

void func(int&)
{
cout << "lvalue" << endl;
}

void func(int&&)
{
cout << "rvalue" << endl;
}

template <typename T>
void wrapper(T&& value)
{
func(std::forward<T>(value));
}

int main()
{
int x = 10;
wrapper(x);
wrapper(20);
return 0;
}

输出:

1
2
lvalue
rvalue

这就是“原样转发”的效果。

4. 万能引用和完美转发

T&& 在模板推导场景下,常被称作万能引用转发引用
它能根据传入参数是左值还是右值,推导出不同的类型,再配合 std::forward 实现完美转发。

5. 典型使用场景

5.1 工厂函数

1
2
3
4
5
template <typename T, typename... Args>
std::unique_ptr<T> makeObj(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

5.2 包装器和适配器

只要你的函数只是“接一下参数,再转交给别人”,大概率就会涉及完美转发。

6. std::move 和 std::forward 的区别

std::move

  • 无条件把对象转成右值

std::forward

  • 有条件地保持原始值类别
  • 主要用于模板转发场景

所以中转模板里通常用 forward,不是 move

总结

完美转发是现代 C++ 模板编程里很常见的一项能力。
它的目标不是“把东西都变成右值”,而是“别把原来传进来的左值/右值语义弄丢”。理解了这一点,再看 std::forward 就会清楚很多。