request-free-img

C++中的完美转发:让新手不再一头雾水

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类中,移动构造函数内部就会执行“资源窃取”操作。从而实现完美转发 的过程。

对于完美转发,现在你明白了吗?


更多问题探讨,请关注公众号:程序员角