C++中std::ref和std::cref有什么用_C++模板传参引用封装【笔记】

2次阅读

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

C++中std::ref和std::cref有什么用_C++模板传参引用封装【笔记】

std::ref 用来把左值转成可拷贝的引用包装器

在模板函数或泛型算法中,如果参数是按值传递的(比如 std::functionstd::Threadstd::bind),直接传入普通引用变量会导致“引用被退化为值”,即发生拷贝而非引用绑定。这时候 std::ref 就派上用场了——它把左值包装成一个可拷贝的对象,内部仍持有原始变量的引用。

常见错误现象:std::thread 构造时传入引用参数却没用 std::ref,导致线程里操作的是副本,主线程变量不变。

  • 只接受左值:不能对临时量或字面量用 std::ref(42),编译失败
  • 包装后类型是 std::reference_wrapper<t></t>,支持隐式转为 T&
  • std::function 配合时,能避免捕获引用失效(尤其在 Lambda 被存储后延迟调用)

std::cref 专用于只读引用封装

std::crefstd::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::refstd::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 能解决所有“传引用”问题,但它无法跨线程安全地共享非原子变量——包装器本身线程安全,但被引用的变量仍需同步。别把它当成线程安全的银弹。

text=ZqhQzanResources