📌深入理解 C++ 中的 std ref 和 cref 的引用封装机制
深入理解 C++ 中的 std ref 和 cref 的引用封装机制
在现代 C++ 编程中,按值传参是常态,但有时候我们确实需要传引用。尤其是在使用
std::bind
、std::thread
、标准算法等场景下,如果不加注意,原本希望传引用的变量却被复制,导致逻辑失效甚至程序崩溃。
本文将带你深入理解std::ref
和std::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::ref
,a
会被拷贝,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
不会延长原对象的生命周期 -
ref
和cref
都只能绑定左值
使用注意事项
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::ref
和 std::cref
是现代 C++ 中非常实用的小工具,它们让你能够安全、显式地在值语义上下文中传递引用,避免拷贝、保持语义清晰。
在多线程、函数绑定、算法调用等场景下,它们能够显著提高代码的正确性与表达力。
下次遇到按值调用的问题,不妨试着想一想:这里是不是该用 std::ref
或 std::cref
?
📚 推荐阅读:
- cppreference:std::ref
- 《Effective Modern C++》第 22 条:避免意外按值传参