C++中的完美转发绝对是让很多新手C++程序员一头雾水的存在。
今天这个视频我要和你弄清楚这个C++11引入的强大特性。

完美转发到底解决了什么问题?
要清楚完美转发,首先我们需要明白,它的引入到底解决了什么问题,请看这段代码:
Widget createWidget(int a, string name) {
return Widget(a, name); // 直接用收到的参数造一个 Widget
}
string s = “hello”; // s 是一个“有名字的东西”(左值)
createWidget(10, s); // 用现有的 s
createWidget(10, string(“world”)); // 临时造一个字符串(右值,很快就没了)
在这段简单的代码中,我们有一个createWidget函数,它接受一个Int参数和一个string参数来构建一个Widget对象。
在调用createWidget时,第一个调用我们传入了一个左值,第二个调用我们传入一个临时构造的字符串也就是右值。
现在问题来了:
在createWidget 函数里面,name 这个参数它是有名字的,参数一旦有了名字,无论外面传进来的是左值还是右值,它本身都会被当成左值来对待。
它的结果就是:即使你传的是临时的字符串,它也只能老老实实复制一份。这样就白白浪费了一次复制,导致效率很低。
所以这样的转发是不完美的。
C++11的两个关键技巧
为了解决这个问题,C++11带来了两个小技巧。
1. 万能引用 T&&
第一个是接收的时候使用:“万能引用” T&&
这个T&& 很神奇,它一点也不挑食:
- 你传左值→ 它自动变成左值引用
- 你传右值→ 它自动保持成右值引用
它就像一个“透明的盒子”,东西放进来是什么样,拿出去还是什么样。
2. std::forward 完美转发
第二个转交的时候用 std::forward
std::forward<Args>(args) 的作用是:
- 如果原来是左值 → 转交的时候还是左值
- 如果原来是右值 → 转交的时候还是右值
这样,如果传进来的是临时对象,那么就能直接“搬走”了,不用复制,省时间省空间。
完美转发的实际写法
对于上边的示例代码,我们使用完美转发稍微改一下就变成这样:
template<typename... Args> // 可以收任意多个参数
Widget createWidget(Args&&... args) // 注意这里的 &&,叫“万能引用”
{
// 把收到的东西完美转给真正的构造函数
return Widget(std::forward<Args>(args)...);
}
string s = “hello”; // s 是一个“有名字的东西”(左值)
createWidget(10, s); // 用现有的 s
createWidget(10, string(“world”)); // 临时造一个字符串(右值,很快就没了)
现在当我们传入左值时它仍然会进行拷贝:这里补充说明一下,因为左值通常是已有对象,我们必须保证不拿走它的资源,所以只能拷贝,这是正确的行为。
而当我们传入右值时:它会使用完美转发,直接移动!而不用拷贝。
白话总结
总之用白话说就是:
用 T&&(万能引用)收参数,能记住原来是左值还是右值。
再用 std::forward<T>转交参数,就能把原来的“左值/右值”属性完美还原回去,让临时对象可以被移动,而不是复制。
它的核心目的是避免不必要的拷贝,它既能让代码更短、更通用,又能自动选择最高效的构造方式,是现代 C++ 中写工厂函数、emplace 函数、转发包装器的标准姿势。
比如 std::make_unique、std::make_shared、emplace_back 内部都是用完美转发实现的。
完美转发的底层原理
那它是如何做到的呢?其实也很简单。
在我们这句函数调用中,std::string(“world”) 这里会创建一个临时 std::string 对象。
这个临时对象作为右值传入 createWidget。
由于参数是 Args&&… args(万能引用),模板推导后 Args 变为 int 和 std::string。
std::forward看到 参数是非引用std::string,就知道它是一个右值,于是把 args强制转为右值,也就是这样 static_cast<std::string&&>(args)。
展开后相当于:Widget(10, static_cast<std::string&&>(临时对象))
这样就会精确匹配到 Widget 类的移动构造函数。
而在Widget类中,移动构造函数内部就会执行“资源窃取”操作。从而实现完美转发 的过程。
对于完美转发,现在你明白了吗?


