目录
[1.1 什么是包装器](#1.1 什么是包装器)
[1.2 为什么需要包装器](#1.2 为什么需要包装器)
[2.1 为什么引用不能放进容器](#2.1 为什么引用不能放进容器)
[2.2 reference_wrapper 的出现](#2.2 reference_wrapper 的出现)
[2.3 ref的使用](#2.3 ref的使用)
[3.1 什么是bind](#3.1 什么是bind)
[3.2 占位符](#3.2 占位符)
[3.3 bind 的使用](#3.3 bind 的使用)
[3.4 bind 底层原理](#3.4 bind 底层原理)
[3.5 bind 的缺点](#3.5 bind 的缺点)
[4.1 什么是function](#4.1 什么是function)
[4.2 function 的使用](#4.2 function 的使用)
一、包装器
1.1 什么是包装器
包装器是对已有对象进行再次封装,使其拥有新的行为或接口。
例如:指针包装成智能指针,函数包装成函数对象,引用包装成对象。
核心思想:原始对象 -> 经过包装器包装 -> 获得新的能力
1.2 为什么需要包装器
举几个经典问题:
问题1
模板无法直接存储引用,vector<int&> 非法,于是出现了 reference_wrapper<int> 。
问题2
不同类型函数无法统一存储
对于普通函数、Lambda、仿函数、成员函数它们的类型完全不同,需要 std::function 统一包装。
问题3
参数顺序固定,func(a, b, c) 想提前绑定部分参数,需要 std::bind 。
二、reference_wrapper
2.1 为什么引用不能放进容器
引用的特点:必须初始化,不可重新绑定。对于 vector<int&> 无法实现,是因为 vector 不是一个个对象尾插的,它底层是2倍或1.5倍扩容机制,这就导致了vector多扩容的部分对象无法初始化。假如不存在扩容机制,只是一个个扩容,那么对于 v.push_back(a); v.back() = c; 这份代码意味着,v 的最后一个元素引用了a对象,但是 v.back() = c; 意味着将 c 的值赋值给了 v 的最后一个元素,不是引用重新绑定了 c 对象。由于vector底层有时扩容会释放旧空间,开辟新空间,这一点也是无法解决的。
2.2 reference_wrapper 的出现
对于上述问题,C++98的解决方案是容器不存引用,而是存指针。
vector<int*> v;
v.push_back(&a);
v.push_back(&b);
这样做虽然可以达到目的,但是使用不方便,如 (*v0)++; 需要解引用。
于是标准库提供了
template <class T> class reference_wrapper;
它存在于 <functional> 这个头文件中。
它的本质就是:引用的对象化,把引用包装成一个类,底层原理就是包装指针的类。
示例:
cpp
#include <iostream>
#include <functional>
using namespace std;
int main()
{
int a = 0;
reference_wrapper<int> ra = a;
ra.get() = 100;
cout << a << endl;
return 0;
}
2.3 ref的使用
在实际开发中几乎不会这样写
reference_wrapper<int> ra = a;
而是
ref(a)
例如:
int a = 10;
auto rw = std::ref(a);
ref 就是一个构造 reference_wrapper对象的函数。

学到这里,就能解决容器中如何存引用的问题。
cpp
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
int main()
{
vector<reference_wrapper<int>> v;
int a = 10;
v.push_back(ref(a));
cout << v.back().get() << endl;
int b = 20;
v.back() = b;
cout << v.back() << endl;
return 0;
}
对于 reference_wrapper 和 ref 还有很多其他用途,例如解决 thread 和 bind 传引用的问题。
三、bind
3.1 什么是bind

std::bind 是一个函数模板,用于生成可调用对象的包装器(函数适配器)。它接收一个可调用对象,并根据参数绑定规则(值绑定、引用绑定、占位符等)生成一个新的可调用对象。
该新可调用对象可以对原函数的参数进行绑定、重排或部分固定,从而改变函数的调用形式。
基本语法:auto newCallable = bind(callable, arg_list);其中 newCallable 本身是一个可调用对象,用来接受bind返回的可调用对象,callable 是bind处理的目标对象,arg_list 是参数绑定规则。
3.2 占位符
arg_list 中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是占位符,表示 newCallable的参数,它们占据了传递给 newCallable 的参数位置。数值n表示生成的可调用对象中参数的位置:_1 为newCallable的第一个参数,_2 为第二个参数,以此类推,_n 为第n个参数。
这些占位符存放在了placeholders 的一个命名空间中。

3.3 bind 的使用
cpp
// 基本用法:绑定函数 + 固定参数
int add(int a, int b)
{
return a + b;
}
auto f = std::bind(add, 10, 20);
cout << f(); // 30
这里发生的是:add 函数经过 bind 函数的处理,返回一个新的可调用对象,其中可调用对象的第一个参数被固定为 10,第二个参数被固定为 20,所以 f 就不需要传递参数,也不能传递参数。
cpp
// 使用占位符
using namespace std::placeholders;
auto f = std::bind(add, 10, _1);
f(5);
调用 f(5); 等价于add(10, 5);
只需记住一句话,_1 表示:调用时再传进来的第一个参数。其中_1 跟add没有关系,它不是代表add的第一个参数。
cpp
// 参数重排
auto f = std::bind(add, _2, _1);
f(10, 20);
调用 f(10, 20); 等价于 add(20, 10);
因为_1 是f(10,20) 的第一个参数,_2 是f(10, 20)的第二个参数。
cpp
// 忽略参数
auto f = std::bind(add, _1, 100);
f(5);
调用 f(5); 等价于 add(5, 100);
cpp
// 绑定成员函数
struct Test
{
int add(int x)
{
return x + 10;
}
};
Test t;
auto f = std::bind(&Test::add, &t, _1);
调用 f(5); 等价于 t.add(5);
对于成员函数,它们的参数列表会隐含地多一个this指针指向要调用成员函数的对象,所以必须要传一个对象的地址。
cpp
// bind + ref
int x = 10;
auto f = std::bind(add, std::ref(x), _1);
作用:对于bind默认生成的可调用对象,都是传值传参,但对上述代码来说,不是拷贝 x 而是引用x,可以修改 x 的值。
3.4 bind 底层原理
bind 返回的到底是什么?
bind 返回的是一个匿名函数对象(仿函数)。
可以理解为它内部生成了类似的类
cpp
class BindObject
{
FunctionType func; // 原函数
tuple stored_args; // 绑定的参数
public:
template<class... CallArgs>
auto operator()(CallArgs&&... call_args)
{
return invoke(func, stored_args, call_args...);
}
};
bind的核心结构可以拆成 3 层
第一层:保存可调用对象 (普通函数、lambda、成员函数、函数对象)
第二层:保存参数绑定规则 (值绑定、引用绑定、占位符)
第三层:调用时展开参数 (依次给可调用对象传递保存的参数)
3.5 bind 的缺点
- 可读性差
bind(add, _2, _1)
对于不明白的人,跟可能认为 _1 为add的第一个参数,_2 为add的第二个参数,不直观
- 错误信息难看
bind为一个函数模板,且返回一个新的可调用对象,这个过程是十分复杂的,出错了不容易排查
- lambda 可以完全替代
实际场景中,没有人会用bind来调整函数参数顺序,都是用来固定某个参数,对于lambda也可以很简洁地完成该功能。
cpp
auto f = [](int x){
return add(10, x);
};
四、function
4.1 什么是function

function是一个类模板,也是一个包装器。function实例化出的对象可以包装并存储可调用对象,包括函数对象、仿函数、lambda、bind表达式、成员函数、普通函数等,存储的可调用对象被称为function的目标。若function不含目标,则它为空,调用空function的目标会抛出bad_function_call 异常。
它也被定义在<functional>的头文件中。
它主要的功能是:把不同类型的可调用对象统一成一个类型。
基本语法:function<返回值(参数)> f;
4.2 function 的使用
普通函数
cpp
int add(int x, int y) { return x + y; }
std::function<int(int, int)> f1 = add;
f1(2,5);
lambda
cpp
std::function<int(int, int)> f2 = [](int x, int y) { return x + y; };
f2(3, 4);
仿函数
cpp
struct Functor
{
int operator()(int x, int y)
{
return x + y;
}
};
std::function<int(int, int)> f3 = Functor();
f3(1, 3);
bind表达式
cpp
std::function<int(int, int)> f4 = std::bind(add, 10, std::placeholders::_1);
f4(2);
最常用的场景之一
对于对某些特定的指令,执行特定函数。
cpp
// function作为map的映射可调用对象的类型
map<string, function<int(int, int)>> opFuncMap = {
{"+", [](int x, int y){return x + y;}},
{"-", [](int x, int y){return x - y;}},
{"*", [](int x, int y){return x * y;}},
{"/", [](int x, int y){return x / y;}}
};
