std::ref用于将左值包装为可拷贝的std::reference_wrapper,避免模板中引用退化为值;std::cref同理但封装const引用;二者支持隐式转换和函数对象调用,但需注意生命周期、线程安全及模板推导陷阱。

std::ref 用来把左值转成可拷贝的引用包装器
在模板函数或泛型算法中,如果参数是按值传递的(比如 std::function、std::thread、std::bind),直接传入普通引用变量会导致“引用被退化为值”,即发生拷贝而非引用绑定。这时候 std::ref 就派上用场了——它把左值包装成一个可拷贝的对象,内部仍持有原始变量的引用。
常见错误现象:std::thread 构造时传入引用参数却没用 std::ref,导致线程里操作的是副本,主线程变量不变。
- 只接受左值:不能对临时量或字面量用
std::ref(42),编译失败 - 包装后类型是
std::reference_wrapper<t></t>,支持隐式转为T& - 和
std::function配合时,能避免捕获引用失效(尤其在 lambda 被存储后延迟调用)
std::cref 专用于只读引用封装
std::cref 和 std::ref 行为一致,但返回的是 const T& 的包装器,适用于你明确不希望被修改的场景,比如传给只读回调、只读容器视图或 std::function<void int></void> 这类签名。
使用场景举例:向 std::for_each 传递一个只读上下文对象,又不想意外被算法内部修改。
立即学习“C++免费学习笔记(深入)”;
- 对 const 左值自动推导更安全,比如
const int x = 42; std::cref(x)明确表达意图 - 若误用
std::ref包装 const 变量,会编译失败(因为std::ref要求非 const 引用) - 和
std::cref包装的变量在解引用后仍是 const,赋值操作会被编译器拦截
std::reference_wrapper 的隐式转换和函数对象特性
std::ref 和 std::cref 返回的都是 std::reference_wrapper 类型,这个类型不是指针也不是智能指针,而是一个轻量级引用包装器,核心特点是支持隐式转换回原引用类型,并且重载了 operator(),因此本身可当函数对象用(常用于容器中存引用)。
容易踩的坑:把 std::reference_wrapper 存进 std::vector 后,遍历时如果不解引用(比如写成 v[i] = 10 而不是 v[i].get() = 10 或直接 v[i] = 10 —— 实际上 v[i] = 10 是合法的,因为隐式转换+赋值重载),可能误以为没生效。
- 支持
.get()显式获取引用,但多数时候不需要,因为隐式转换已覆盖大部分使用场景 - 不能用在需要真实指针的地方(比如
&std::ref(x)没有意义,得到的是 wrapper 对象的地址) - 在
std::vector<:reference_wrapper>></:reference_wrapper>中,元素大小仍是固定(通常 8 字节),不随所引用对象变大
模板推导中 ref/cref 影响类型匹配
当模板函数形参是 T&&(万能引用),传入 std::ref(x) 时,T 推导为 std::reference_wrapper<x></x>,而不是 X&。这意味着如果你依赖模板参数还原出原始引用类型,必须额外处理 std::reference_wrapper 特化。
性能影响很小,但兼容性要注意:某些老库或自定义模板可能没针对 std::reference_wrapper 做偏特化,导致转发失败或静默退化为值传递。
- 检查是否被正确转发:在模板内用
static_assert(std::is_reference_v<decltype>)</decltype>不够,得先std::unwrap_ref_decay_t或手动std::remove_reference_t<:remove_cv_t>></:remove_cv_t>处理 - C++20 起推荐用
std::unwrap_ref_decay_t<t></t>统一处理引用包装器和普通类型 - 别在返回值中裸用
std::ref:函数返回std::ref(x)是悬空引用,x 生命周期结束就完了
最常被忽略的一点:很多人以为 std::ref 能解决所有“传引用”问题,但它无法跨线程安全地共享非原子变量——包装器本身线程安全,但被引用的变量仍需同步。别把它当成线程安全的银弹。










