📌深入理解 C++ 中的 std ref 和 cref 的引用封装机制

深入理解 C++ 中的 std ref 和 cref 的引用封装机制

在现代 C++ 编程中,按值传参是常态,但有时候我们确实需要传引用。尤其是在使用 std::bindstd::thread、标准算法等场景下,如果不加注意,原本希望传引用的变量却被复制,导致逻辑失效甚至程序崩溃。
本文将带你深入理解 std::refstd::cref 的设计原理、使用场景以及易踩的坑。


为什么需要引用封装器

先来看一个例子:

void increment(int& x) {
    x++;
}

int main() {
    int a = 5;
    std::thread t(increment, a);  // ❌ 编译失败
    t.join();
}

你可能以为把 a 传进去就是引用了,但事实并非如此。std::thread 默认按值复制参数。它尝试将 a 拷贝一份传递给 increment,而 increment 期望的是 int&,于是编译器报错。

这个时候,std::ref(a) 就派上了用场:

std::thread t(increment, std::ref(a));  // ✅ 传引用成功

std ref 和 cref 是什么

  • std::ref(obj):返回一个 可修改引用 的包装器。

  • std::cref(obj):返回一个 const 引用 的包装器。

这两个函数本质上返回的是一个 std::reference_wrapper<T> 类型,它可以模拟“按引用传参”的行为,但仍然以“按值”方式传递给调用者。


典型使用场景

1. std::thread

void print(int& x) {
    std::cout << x << std::endl;
}

int main() {
    int a = 42;
    std::thread t(print, std::ref(a));  // ✅ 按引用传入
    t.join();
}

如果不使用 std::ref,代码会因为引用绑定失败而无法通过编译。


2. std::bind

#include <functional>

void set_to_100(int& x) {
    x = 100;
}

int main() {
    int a = 0;
    auto f = std::bind(set_to_100, std::ref(a));
    f();  // ✅ 成功修改 a
}

若不使用 std::refa 会被拷贝,set_to_100 内部修改的是拷贝,而不是原变量。


3. 标准算法

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>

void print(int x) { std::cout << x << " "; }

int main() {
    std::vector<int> data{1, 2, 3};
    std::for_each(data.begin(), data.end(), std::ref(print));  // ✅ 引用函数
}

cref 的使用场景

当你希望以只读引用方式传参时,使用 std::cref

void show(const int& x) {
    std::cout << x << std::endl;
}

int main() {
    int val = 10;
    auto f = std::bind(show, std::cref(val));
    f();  // 输出 10
}

实现原理简析

标准库中的 reference_wrapper 本质上就是用一个指针模拟引用:

template<typename T>
class reference_wrapper {
public:
    explicit reference_wrapper(T& ref) noexcept : ptr(std::addressof(ref)) {}
    T& get() const noexcept { return *ptr; }
    operator T&() const noexcept { return *ptr; }
private:
    T* ptr;
};

这也意味着:

  • reference_wrapper 不会延长原对象的生命周期

  • refcref只能绑定左值


使用注意事项

1. 不可绑定右值

std::ref(42);     // ❌ 错误:不能对临时对象创建可修改引用
std::cref(42);    // ✅ 正确:const 引用可以绑定右值

2. 生命周期问题

不要对临时变量使用 std::ref

auto f = std::bind(print, std::ref(temp()));  // ❌ 悬垂引用

应用总结

场景 默认传值? 是否推荐使用 ref/cref
std::thread
std::bind
std::function ✅(捕获拷贝)
for_each / 算法
Lambda 捕获 可选(值/引用) ❌ 推荐 [&] 捕获

结语

std::refstd::cref 是现代 C++ 中非常实用的小工具,它们让你能够安全、显式地在值语义上下文中传递引用,避免拷贝、保持语义清晰。
在多线程、函数绑定、算法调用等场景下,它们能够显著提高代码的正确性与表达力。

下次遇到按值调用的问题,不妨试着想一想:这里是不是该用 std::refstd::cref


📚 推荐阅读: