std::ref用于包装左值以在模板函数中保留引用语义,返回可拷贝的std::reference_wrapper对象,支持隐式转换为T&;std::cref同理但专用于const引用;需用.get()解包或类型特征识别。

std::ref 用来包装左值,让模板函数接收引用而非拷贝
当你把一个变量传给模板函数(比如 std::thread、std::bind 或泛型算法),编译器默认按值传递 —— 即使参数声明为引用,模板推导也会“吃掉”引用性。这时 std::ref 就是显式告诉编译器:“请保留这个左值的引用语义”。
它返回一个 std::reference_wrapper 对象,该对象可隐式转换为 T&,也能被拷贝(内部存的是指针)。
常见错误现象:
int x = 42;
std::thread t([](int& a) { a = 100; }, x); // 编译失败:无法将 'int' 绑定到 'int&'因为 x 被当作右值传入,模板推导出 int 类型,无法绑定非常量左值引用。
正确做法:
- 用
std::ref(x)包装,让类型变成std::reference_wrapper - 接收参数改为
std::reference_wrapper或直接用int&(靠隐式转换) - 注意:不能对右值(如
std::ref(42))使用 —— 编译报错no matching constructor
int x = 42;
std::thread t([](int& a) { a = 100; }, std::ref(x));
t.join();
// x 现在是 100std::cref 专用于只读引用,避免意外修改
和 std::ref 行为一致,但返回 std::reference_wrapper,确保接收端只能以 const T& 方式访问。适合传 const 引用参数、或你明确不希望函数修改原变量的场景。
立即学习“C++免费学习笔记(深入)”;
使用场景:
- 传给只接受
const int&的模板函数 - 配合
std::bind绑定常量参数时防止误写 - 比手动写
const_cast安全,且语义清晰
错误示例:
int y = 99;
std::thread t([](const int& a) { /* 读取 a */ }, std::ref(y)); // 可编译,但失去 const 保护虽然能过,但 std::ref(y) 是非 const 的,如果 lambda 内部误转成非常量引用就危险。
更稳妥写法:
int y = 99;
std::thread t([](const int& a) { /* 只读 */ }, std::cref(y));模板中怎么识别并解包 std::reference_wrapper?
如果你自己写模板函数,需要支持 std::ref/std::cref 传入,就不能只写 T& —— 因为实际传进来的是 std::reference_wrapper。得靠类型特征或重载来处理。
最简单可靠的方式是使用 std::decay_t + 引用折叠,或直接用 auto&& 参数配合 .get() 显式解包:
-
std::reference_wrapper返回::get() T&,安全无开销 - 不要依赖自动转换做多次转发;中间层若未声明接受
std::reference_wrapper,会静默转成值 - C++17 起可用
std::is_reference_v<:unwrap_reference_t>>判断是否包裹了引用
templatevoid log_value(T&& arg) { using U = std::remove_reference_t ; if constexpr (std::is_same_v>>) { std::cout << "ref-wrapped: " << arg.get() << "\n"; } else { std::cout << "plain: " << arg << "\n"; } }
std::ref 和 std::cref 的生命周期风险
它们不管理所引用对象的生命周期,只是轻量级包装器(本质是 const T*)。如果原对象提前析构,再通过 .get() 访问就是悬垂引用 —— 行为未定义,且编译器几乎不报错。
典型踩坑点:
- 把局部变量用
std::ref传给异步任务(如std::thread),但主线程很快退出,局部变量销毁 - 存储
std::reference_wrapper到容器里,却没保证被引用对象活得比容器久 -
std::cref同样有这问题,const 不等于安全
解决思路只有人工保证:传之前确认对象生命周期 ≥ 接收方使用周期。没有 RAII 方案能自动兜底。
复杂点在于,这种错误往往只在特定调度顺序下复现,调试困难。宁可多拷贝一次,也别让引用跨作用域乱跑。










