1.function
1.1 function包装器介绍
function是一种函数包装器,也叫做适配器。它可以对可调用对象进行包装,C++中的function本质就是一个类模板。
function类模板的原型如下:
cpp
template <class T>
class function; // undefined
//只要你传入的 T 不是我后面特化的类型,就直接编译报错!
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret:被包装的可调用对象的返回值类型。
Args...:被包装的可调用对象的形参类型。
cpp
#include<functional>
//1.函数
int f(int a, int b)
{
return a + b;
}
//2.仿函数
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
//4.类成员函数
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{}
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};
int main()
{
// 包装各种可调⽤对象
function<int(int, int)> f1 = f;//1
function<int(int, int)> f2 = Functor();//2
function<int(int, int)> f3 = [](int a, int b) {return a + b; };//3
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
// 4.包装静态成员函数
// 成员函数要指定类域并且前⾯加&才能获取地址
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
// 包装普通成员函数
// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
//1.传对象指针
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
//2.传对象
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
//3.传右值 / 临时对象
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.1, 1.1) << endl;
cout << f7(Plus(), 1.1, 1.1) << endl;
return 0;
}
| function 模板参数 | 第一个参数传什么 |
|---|---|
| double(Plus*, ...) | 对象指针 &pd |
| double(Plus, ...) | 对象 pd |
| double(Plus&&, ...) | 临时对象 /move 对象 |
std::function 是⼀个类模板,也是⼀个包装器。 std::function 的实例对象可以包装存
储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调⽤对象被称为std::function 的⽬标。若std::function 不含⽬标,则称它为空。调⽤空std::function 的⽬标导致抛出std::bad_function_call异常。
1.2function包装器统一类型
函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型
cpp
#include <iostream>
#include <functional>
#include <vector>
using namespace std;
// 1. 普通函数
int add(int a, int b) {
return a + b;
}
// 2. 仿函数(函数对象)
struct Multiply {
int operator()(int a, int b) {
return a * b;
}
};
// 3. 类 + 成员函数
class MyClass {
public:
int sub(int a, int b) {
return a - b;
}
};
int main() {
// 重点:用 function 把四种完全不同的可调用对象
// 全部包装成同一种类型:function<int(int, int)>
// 1. 包装普通函数
function<int(int, int)> f1 = add;
// 2. 包装 lambda
function<int(int, int)> f2 = [](int a, int b) { return a - b; };
// 3. 包装仿函数
function<int(int, int)> f3 = Multiply();
// 4. 包装类成员函数(第一个参数是对象/指针)
MyClass obj;
function<int(MyClass*, int, int)> f4 = &MyClass::sub;
// 放进同一个 vector(统一类型)
vector<function<int(int, int)>> vec;
vec.push_back(f1);
vec.push_back(f2);
vec.push_back(f3);
// 调用
cout << vec[0](10, 20) << endl; // add 30
cout << vec[1](10, 20) << endl; // lambda -10
cout << vec[2](10, 20) << endl; // Multiply 200
return 0;
}
std::function 就是一个 "类型适配器",把所有可调用对象,包装成统一的类型
1.3function包装器的意义
将可调用对象的类型进行统一,便于我们对其进行统一化管理。
包装后明确了可调用对象的返回值和形参类型,更加方便使用者使用。
2.bind包装器
bind也是一种函数包装器,也叫做适配器。它可以接受一个可调用对象,生成一个新的可调用对象来"适应"原对象的参数列表,C++中的bind本质是一个函数模板。
1.可以固定部分参数
2.可以调整参数顺序
3.可以把成员函数转成普通函数
4.最终得到一个可直接调用的新函数对象
bind函数模板的原型如下:
cpp
template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
Fn:要绑定的可调用对象类型(函数、lambda、成员函数、仿函数)
Args&&...:可变参数模板,支持任意个数、任意类型的参数
Ret:手动指定返回值类型
调⽤bind的⼀般形式 : auto newCallable = bind(callable,arg_list);
其中newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3...这些占位符放到placeholders的⼀个命名空间中。
2.1用法
bind包装器绑定固定参数
1.无意义的绑定
cpp
int Plus(int a, int b)
{
return a + b;
}
int main()
{
//无意义的绑定
function<int(int, int)> func = bind(Plus, placeholders::_1, placeholders::_2);
cout << func(1, 2) << endl; //3
return 0;
}
绑定时第一个参数传入函数指针这个可调用对象,但后续传入的要绑定的参数列表依次是placeholders::_1和placeholders::_2,表示后续调用新生成的可调用对象时,传入的第一个参数传给placeholders::_1,传入的第二个参数传给placeholders::_2。此时绑定后生成的新的可调用对象的传参方式,和原来没有绑定的可调用对象是一样的,所以说这是一个无意义的绑定。
2.绑定固定参数
如果想把Plus函数的第二个参数固定绑定为10,可以在绑定时将参数列表的placeholders::_2设置为10。比如:
cpp
int Plus(int a, int b)
{
return a + b;
}
int main()
{
//绑定固定参数
function<int(int)> func = bind(Plus, placeholders::_1, 10);
cout << func(2) << endl; //12
return 0;
}
2.bind包装器调整传参顺序
想要将sub成员函数用于相减的两个参数的顺序交换,那么直接在绑定时将placeholders::_1和placeholders::_2的位置交换一下就行了。比如:
cpp
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
//调整传参顺序
function<int(int, int)> func = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
cout << func(1, 2) << endl; //1
return 0;
}
3.绑定成员函数
bind 绑成员函数时,第二个位置必须传对象 / 指针 / 引用,它就是用来代替隐藏的 this 指针!
cpp
class MyClass {
public:
int sub(int a, int b) {
return a - b;
}
};
MyClass obj;
auto f3 = bind(&MyClass::sub, obj, _1, _2);
cout << f3(10, 3) << endl; // 10-3=7
相当于
cpp
f3(10, 3);
// 内部实际调用:
obj.sub(10, 3);
注意
std::bind 能够不拷贝,依靠四个核心机制:
可变参数模板:接收任意参数
通用引用(T&&)+ 引用折叠:保留值类别
完美转发:保持左值 / 右值属性,不强制拷贝
std::reference_wrapper:显式传递引用,实现真正零拷贝
std::reference_wrapper 的作用:
绑定类成员函数时,用引用包装器存储对象地址,避免拷贝对象;
调用时自动解包为原对象引用,最终正确调用该对象的成员函数。