C++ / a working model

49 / 80   ·   C++11   ·   约 10 分钟

转发引用与 perfect forwarding

先记住这句话

转发引用配合模板推导记录调用者传入的值类别,std::forward 再把这份信息用于下一次调用。它适合透明包装,不是所有 T&& 的别名,也不是比 std::move 更高级的通用替代品。

本篇内容
  1. 先判断是否真的在推导
  2. 具名参数会丢失调用表达式类别
  3. 完美转发有适用边界
  4. 运行示例
  5. 动手练习

先判断是否真的在推导

典型转发引用是函数模板中由本次调用推导的、不带 cv 限定的类型参数 T&&。传入左值时 T 可以推导成 U&,传入右值时 T 推导为 U。const T&& 不是转发引用;类模板中已经固定的 T 所构成的成员函数参数 T&& 也不因此成为转发引用。

推导后可能出现逻辑上的“引用套引用”,语言用引用折叠消除它:只要任一层是左值引用,结果就是左值引用;只有右值引用与右值引用组合才仍是右值引用。因此包装函数能以同一声明接收左值、const 左值与右值。

具名参数会丢失调用表达式类别

在包装函数内部,参数 x 已有名字,表达式 x 是左值。如果直接 target(x),所有调用都倾向左值重载。std::forward<T>(x) 利用 T 中保存的信息,有条件地恢复左值或右值类别,并保留 const 等类型属性。

示例为目标定义三个重载,分别返回可修改左值、只读左值、右值的标记。转发包装保留三种选择;另一个不转发的包装则展示右值进入函数后也会成为具名左值。这样可观察转发的功能,而不依赖资源分配次数或编译器优化。

完美转发有适用边界

转发适合工厂、容器就地构造和调用适配层:这一层不决定是否消费对象,只忠实传递调用者意图。若接口明确接收并消耗一个已知类型,普通 T&& 配合 std::move 更直观;若只需要读数据,const T& 可能已经足够。

“完美”不意味着所有语法都能透明转发。未指定类型的花括号列表、重载函数集合和某些位域无法按普通 T&& 推导。转发同一参数两次还可能让第一个调用消费资源,第二个看到移动后状态。应让接口需求决定转发范围,而不是默认把每个参数都写成万能形式。

容易答错的地方

  • 把 std::forward<T>(x) 换成 std::move(x) 会把调用者的左值也强制作为右值交出,破坏包装层的透明性。
  • 已固定类型的 T&& 与 const T&& 不是本节所述转发引用;不能仅凭两个 & 符号判断。

运行一个例子

最低标准 C++11 · 完整程序 · 下载 .cpp

#include <cassert>
#include <iostream>
#include <utility>

int select(int&) { return 1; }
int select(const int&) { return 2; }
int select(int&&) { return 3; }
template<class T>
int relay(T&& x) { return select(std::forward<T>(x)); }
template<class T>
int named(T&& x) { return select(x); }

int main() {
    int n = 1;
    const int c = 2;
    assert(relay(n) == 1);
    assert(relay(c) == 2);
    assert(relay(3) == 3);
    assert(named(3) == 1);
    std::cout << relay(n) << ' ' << relay(c) << ' ' << relay(3) << '\n';
}

在本地编译

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread modern-forward.cpp -o example && ./example

预期结果

1 2 3

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

调用 relay(n) 时 T 与最终参数类型各是什么?调用 relay(std::move(n)) 时又是什么?

查看参考答案

第一种 T 为 int&,T&& 经折叠得到 int&,forward 的结果也是左值。第二种 T 为 int,参数类型 int&&,forward 的结果是将亡值。两次调用仍引用同一个 n;本例目标只返回标记,没有真正修改或消费它。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录