c++ std::reference_wrapper

本文介绍了C++中reference_wrapper的相关知识。它是一个模板类,能包装对象产生类似引用的对象,可拷贝或赋值。还介绍了get函数,用于返回真正的引用。此外,阐述了其特殊用法,如可存入容器、作实参传入函数等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

reference_wrapper

reference_wrapper是一个模板类

template <class T> class reference_wrapper;

用类型T实例化的reference_wrapper类能包装该类型的一个对象,并产生一个reference_wrapper< T >类型的对象,该对象就像是一个引用,与普通引用最大的不同是:该引用可以拷贝或赋值。

特点:没有默认构造函数,可以用一个想要引用的对象构造,或者用另一个reference_wrapper对象构造

int a = 10;
int b = 100;
reference_wrapper<int> r1 = a;
reference_wrapper<int> r2 = r1;		拷贝构造
r1 = 1;			错误,赋值运算符只接收同类型的对象
r1 = b;		    正确,利用 b 构造一个临时对象,再用=运算符赋给 r1
r1 = r2;			正确

get函数

ref()返回的是一个reference_wrapper对象,并不是该对象的引用,所以如果要对返回对象调用成员函数就会报错。使用reference wrapper对象的get()方法,返回真正的引用(实际上reference wrapper是用指针表现出引用的所有特性,所以返回的应该是指针指向的对象)。

通过get成员获取到reference_wrapper所引用的对象,从而进行修改其值等操作。访问所引用的对象的话,直接访问reference_wrapper对象即可

int a = 10;
reference_wrapper<int> r1 = a;
cout << r1 << endl;	// 10
cout << r1.get() << endl;	// 10
r1.get() = 100;
cout << r1.get() << endl;	// 100

特殊用法

可以存入容器中
普通的引用不是对象,所以无法存入任何标准库容器。reference_wrapper包装的引用就可以被存入容器中。

可以像普通的引用一样作为实参传入函数
一个reference_wrapper< T >类型的对象,可以作为实参,传入 形参类型为T的函数中

void func1(int value) { value = 100; }		不过 value 只是个拷贝,不是引用
void func2(int& value) { value = 1000; }	传入的是引用
int a = 10;
reference_wrapper<int> r = a;
func1(r);		传入的是 r 引用的值的 拷贝
cout << a << endl;	 10
func2(r);		传入的是 r 引用的值的 引用
cout << a << endl;	1000

函数func的参数类型为int,而传递给func的参数确是reference_wrapper类型的对象。

  • 若reference_wrapper包裹的引用是可以调用的,则reference_wrapper对象也是可调用的
  • reference_wrapper 常通过引用传递对象给std::bind函数或者std::thread构造函数
  • std::ref 和std::cref 通常用来产生一个reference_wrapper对象,ref()就是用reference_wrapper来包裹对象的一个简化写法。
auto r=ref(o);
//等价于
referencce_wrapper<dectype(o)> r(o);

示例

#include <functional>
using namespace std;

void func(int a, int b)
{
    cout << "a = " << a <<",";
    cout << "b = " << b << endl;
}

void func1(int i)
{
    cout << "i = " << i << endl;
}

int main()
{
    // 包裹函数指针
    std::reference_wrapper<void(int,int)> f0 = func;
    f0(5,7); 		// a = 5,b = 7

    auto f1 = std::ref(func);
    f1(5,7);		// a = 5,b = 7

    // 和bind结合使用
    int i = 10;

    auto f2 = std::bind(func1, i);
    auto f3 = std::bind(func1, std::ref(i));

    i = 30;

    f2();	// i = 10
    f3();	// i = 30

    return 0;
}
<think>我们正在深入探讨`std::ref`和`std::reference_wrapper`的内部原理和工作机制。这个问题涉及到C++标准库中引用语义的封装技术,对于理解函数对象绑定、线程参数传递等场景至关重要。 ### 核心问题:引用语义的封装 C++中引用本质上是别名,但引用本身不是对象(不能重新绑定,不占用存储空间)。当我们希望将引用作为对象传递时(如存入容器、绑定到函数对象),就需要`std::reference_wrapper`。它通过以下机制实现: 1. **存储指针**:内部持有指向目标对象的指针 2. **隐式转换**:提供到`T&`的隐式转换操作符 3. **可调用性**:重载`operator()`支持函数对象的直接调用 ### 实现原理分析 #### 1. 类定义框架 ```cpp template<typename T> class reference_wrapper { public: using type = T; // 构造函数:存储指针 explicit reference_wrapper(T& val) noexcept : ptr_(std::addressof(val)) {} // 禁止绑定到临时对象 reference_wrapper(T&&) = delete; // 隐式转换为原始引用类型 operator T& () const noexcept { return *ptr_; } // 显式获取引用 T& get() const noexcept { return *ptr_; } private: T* ptr_; // 核心:存储对象地址 }; ``` #### 2. `std::ref`的工厂函数 ```cpp template<typename T> reference_wrapper<T> ref(T& t) noexcept { return reference_wrapper<T>(t); } ``` ### 关键特性解析 | 特性 | 原理说明 | |---------------------|--------------------------------------------------------------------------| | **可复制/可赋值** | 类类型对象(非引用语义),支持拷贝和赋值操作 | | **可存储性** | 固定大小(通常为指针大小),可放入标准容器 | | **引用折叠处理** | 模板实例化时正确处理类型推导,避免引用折叠问题[^1] | | **生命周期安全** | 不延长目标对象生命周期,使用者需确保引用有效性 | | **多态支持** | 通过基类指针存储派生类对象,保持虚函数行为 | ### 工作流程示例 考虑以下使用场景: ```cpp void modify(int& x) { x *= 2; } int main() { int value = 42; auto bound = std::bind(modify, std::ref(value)); bound(); // value变为84 } ``` 执行过程: ```mermaid sequenceDiagram participant main as main() participant bind as std::bind participant ref as reference_wrapper participant modify as modify() main->>bind: 传递std::ref(value) bind->>ref: 存储value地址 main->>bound: 调用函数对象 bound->>ref: 解引用获取int& ref->>modify: 传递原始引用 modify->>value: 直接修改原始变量 ``` ### 与原始引用的核心区别 | 特性 | 原始引用 (`T&`) | `std::reference_wrapper<T>` | |-----------------------|----------------------|-----------------------------| | **重绑定** | 不可 | 可(通过赋值) | | **存储容器** | 不可 | 可 | | **类型大小** | 编译时不确定 | `sizeof(T*)` | | **默认构造** | 不可 | 可(需C++20) | | **模板类型推导** | 可能推导为值类型 | 保持引用语义 | ### 高级应用场景 1. **函数组合** ```cpp auto f = [](int x) { return x*2; }; auto g = [](int x) { return x+1; }; std::vector<std::reference_wrapper<decltype(f)>> funcs; funcs.push_back(f); funcs.push_back(g); ``` 2. **多态容器** ```cpp class Base { virtual void f(); }; class Derived : public Base {}; std::vector<std::reference_wrapper<Base>> poly_vec; Derived d; poly_vec.push_back(d); // 保持多态行为 ``` ### 常见误区 1. **绑定临时对象** ```cpp auto bound = std::bind(f, std::ref(42)); // 错误!临时对象立即销毁 ``` 正确做法:确保被引用对象生命周期长于引用包装器 2. **忽略类型转换** ```cpp void process(const std::string& s); char buffer[1024]; auto bound = std::bind(process, std::ref(buffer)); // 错误:类型不匹配 ``` 需要显式转换: ```cpp auto bound = std::bind(process, std::ref(static_cast<std::string&>(buffer))); // 仍然危险,需确保生命周期 ``` ### 性能特点 - **零开销抽象**:优化后与原始指针操作效率相同 - **内联友好**:编译器可完全优化掉包装层 - **无额外内存分配**:仅存储指针,不涉及堆分配 ### 标准演进 | 版本 | 改进要点 | |------------|---------------------------------------------| | C++11 | 首次引入 | | C++17 | 支持`constexpr` | | C++20 | 添加默认构造函数(需显式初始化) | | C++23 | 增强`operator()`的`constexpr`支持 | > **最佳实践**:在需要存储或传递引用语义的场景,优先使用`std::reference_wrapper`替代原始指针,可提供更清晰的类型表达和安全约束[^1]。 §§相关问题§§ 1. 如何在模板元编程中检测`std::reference_wrapper`类型? 2. `std::reference_wrapper`与智能指针在函数绑定中的性能差异如何量化? 3. C++20的`std::bind_front`相比`std::bind`在引用处理上有哪些改进? 4. 为什么lambda捕获引用不需要`std::ref`包装? 5. `std::reference_wrapper`如何影响函数重载决议?</think>### `std::ref` 与 `std::reference_wrapper` 的内部原理与工作机制 #### 核心设计目标 C++ 引用 (`T&`) 本质是**编译期别名**,无法直接存储或传递引用语义。`std::reference_wrapper` 通过**对象化封装**解决此问题: 1. 允许引用作为一等公民(可复制、可存储容器) 2. 保持引用语义(修改影响原对象) 3. 避免不必要的对象拷贝[^1] --- ### 一、内部实现原理 #### 1. 核心数据结构 ```cpp template<typename T> class reference_wrapper { public: using type = T; // 关键:存储原始对象的指针 explicit reference_wrapper(T& val) noexcept : ptr_(std::addressof(val)) {} // 隐式转换为原始引用类型 operator T& () const noexcept { return *ptr_; } // $ \text{转换操作符} $ // 显式获取引用 T& get() const noexcept { return *ptr_; } private: T* ptr_; // 核心存储:目标对象的指针 }; ``` #### 2. `std::ref` 工厂函数 ```cpp template<typename T> reference_wrapper<T> ref(T& t) noexcept { return reference_wrapper<T>(t); // $ \text{自动类型推导} $ } ``` --- ### 二、工作机制详解 #### 工作流程 ```mermaid graph LR A[调用 std::ref(obj)] --> B[构造 reference_wrapper] B --> C[存储 obj 的地址 &obj] C --> D[传递 reference_wrapper 对象] D --> E[隐式转换为 T& 类型] E --> F[操作原始对象 obj] ``` #### 关键特性: | 特性 | 原理说明 | |---------------------|--------------------------------------------------------------------------| | **可复制性** | 类类型对象(非引用语义),支持拷贝和赋值 | | **隐式类型转换** | `operator T&()` 使得在需要引用的场景自动转换 | | **存储安全性** | 存储指针而非引用,避免悬空引用风险(但需用户管理生命周期) | | **多态支持** | 通过基类指针存储派生类对象,保持虚函数行为 | --- ### 三、与标准库组件的交互 #### 1. 在 `std::bind` 中的行为 ```cpp void f(int& x) { x *= 2; } int main() { int val = 10; auto bound = std::bind(f, std::ref(val)); // $ \text{存储 reference\_wrapper} $ bound(); // 隐式转换为 int&,val 变为 20 } ``` - **无 `std::ref`**:`std::bind` 按值存储参数,创建副本 - **有 `std::ref`**:存储 `reference_wrapper`,调用时还原引用 #### 2. 在容器中的使用 ```cpp std::vector<std::reference_wrapper<int>> vec; int a=1, b=2; vec.push_back(a); vec.push_back(b); vec[0].get() = 10; // a 被修改为 10 ``` --- ### 四、编译器视角的转换 考虑以下代码: ```cpp void process(int& x); int main() { int n = 5; auto wrapper = std::ref(n); process(wrapper); // 编译器生成代码 } ``` 编译器实际处理: ```cpp // 编译器生成的等效代码 int n = 5; reference_wrapper<int> wrapper(n); process(wrapper.operator int&()); // 调用转换操作符 ``` --- ### 五、性能分析 1. **零开销抽象**: - 优化后与原始指针操作效率相同 - 无运行时类型检查(静态多态) ```cpp // 生成的汇编示例 (x86-64 GCC) mov rax, QWORD PTR [rbp-8] // 加载指针 mov DWORD PTR [rax], 10 // 直接操作内存 ``` 2. **与原始指针对比**: | 操作 | 原始指针 | `reference_wrapper` | |--------------------|---------------|--------------------| | 解引用访问 | `*ptr` | 隐式转换 | | 类型安全 | 弱 | 强(模板约束) | | 函数参数传递 | 需显式解引用 | 自动转换 | --- ### 六、特殊场景处理 #### 1. 常量性传播 ```cpp const int x = 10; auto ref = std::ref(x); // 类型推导为 reference_wrapper<const int> ref.get() = 20; // 编译错误:常量性保持 ``` #### 2. 函数对象支持 ```cpp auto lambda = [](int x) { return x*2; }; std::vector<std::reference_wrapper<decltype(lambda)>> funcs; funcs.push_back(lambda); // 存储可调用对象的引用 ``` --- ### 七、最佳实践与陷阱 1. **必须遵循**: ```cpp // 正确:绑定到左值 int x = 10; auto ref = std::ref(x); // 错误:绑定到临时对象 auto ref = std::ref(42); // 临时对象立即销毁! ``` 2. **生命周期管理**: ```cpp std::function<void()> create_bound() { int local = 5; return std::bind(f, std::ref(local)); // 危险!local 将销毁 } // 返回的函数对象包含悬空引用 ``` --- ### 总结关键点 1. **存储本质**:内部持有 `T*`,通过转换操作符提供引用语义 2. **类型安全**:模板机制确保类型匹配 3. **零开销**:优化后等效指针操作 4. **应用场景**:函数绑定、容器存储引用、回调机制等需要引用语义的场合 > 在标准库函数(如 `std::bind`, `std::thread`)中传递引用时,**必须使用 `std::ref`** 明确表示引用语义,避免意外的对象拷贝[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值