Function

std::function类是一个包裹可调用对象的类,他是一个模板类,c++中可调用对象有,函数、函数指针、lambda表达式、函数对象以及bind对象。(所有可以使用括号()调用的都是可调用对象)

怎么包裹呢?

// 1. fun 是一个函数
int fun(int a, int b);       

// 2. fun_pointer 是一个函数指针
int (*fun_pointer)(int, int);  

// 3. fun_lambda 是一个lambda表达式
auto fun_lambda = [](int a, int b) -> int {}; 

// 4. // fun_object就是一个函数对象,因为其类重载了()
class Fun_class{
    int operator()(int a, int b);
};
Fun_class fun_object;  

// 5. 跳过 bind 对象先

// 如何绑定上面五种对象呢?
std::function<int(int, int)> f1(fun);
std::function<int(int, int)> f2(fun_pointer);
std::function<int(int, int)> f3(fun_lambda);
std::function<int(int, int)> f4(fun_object);
// 跳过 bind 对象

//// 上面的 f1 f2 f3 f4 就是包裹的 function 对象了,基本上用法也只需要关注 () 即可
f1(1, 2);
f2(3, 4);
f3(4, 5);
f4(5, 6);

// 还有一个用法就是,operator bool,可以判断该对象是否可调用
if(f1){
    f1();
}else{
    cout << "不可调用" << endl;
}

bind

简单用法

bind是一个函数,传入另一个可调用对象,返回一个新的可调用对象,它的原型非常复杂,这里不做解释了,但是下面给一个伪原型吧

function_type bind(function_pointer, Class_Object*, args...);  // 传入一个函数指针,以及一个类的对象地址
function_type bind(function_pointer, args...);  // 只传入一个函数指针

// args... 是可变参数,常常对应于函数指针的参数
  1. 绑定函数
int function_1(int a, int b, int c, int d) {
    std::cout << "调用了 function_1 函数" << std::endl;
    std::cout << "传入参数:[" << a << ", " << b << ", " << c << ", " << d << "]" << std::endl;
    int sum = a + b + c + d;
    std::cout << "返回值:" << sum << std::endl;
    return sum;
}

// f1 叫做 bind 对象
auto f1 = std::bind(function_1, 1, 2, 3, 4);  // 后面的参数个数一定要和原函数对应
f1("乱传参");  // 真正执行f1的时候,这里可以随意传参,但是没什么用的,并不会报错,正常执行

// f1的类型:
std::_Bind_helper<false, int(&)(int, int, int, int), int, int, int, int>::type f1; // 所以还是建议用auto吧
  1. 绑定函数指针
int function_2_(int a, int b, int c, int d) {
    std::cout << "调用了 function_2 函数" << std::endl;
    std::cout << "传入参数:[" << a << ", " << b << ", " << c << ", " << d << "]" << std::endl;
    int sum = a + b + c + d;
    std::cout << "返回值:" << sum << std::endl;
    return sum;
}
int (*function_2)(int, int, int, int) = function_2_;
auto f2 = std::bind(function_2, 1, 2, 3, 4);
f2();  // 真正执行

// f2的类型
std::_Bind_helper<false, int(*&)(int, int, int, int), int, int, int, int>::type f2;
  1. 绑定成员函数和成员变量:没看错,可以绑定到变量上面
class FC {
    int data_1{16};
public:
    int data_2{18};
    int operator()(int a, int b, int c, int d) const {
        std::cout << "调用了 operator() 函数" << std::endl;
        std::cout << "传入参数:[" << a << ", " << b << ", " << c << ", " << d << ", " << data_1 << ", " << data_2 << "]" << std::endl;
        int sum = a + b + c + d + data_1 + data_2;
        std::cout << "返回值:" << sum << std::endl;
        return sum;
    }

    int Function(int a, int b, int c, int d) const {
        std::cout << "调用了 FC::Function() 函数" << std::endl;
        std::cout << "传入参数:[" << a << ", " << b << ", " << c << ", " << d << ", " << data_1 << ", " << data_2 << "]" << std::endl;
        int sum = a + b + c + d + data_1 + data_2;
        std::cout << "返回值:" << sum << std::endl;
        return sum;
    }
};

FC function_3;

// 直接传 function_3 还是跟一个可调用对象一样,所以不需要指针
auto f3_1 = std::bind(function_3, 1, 2, 3, 4);
// 传入成员函数/变量地址和对象地址
auto f3_2 = std::bind(&FC::Function, &function_3, 1, 2, 3, 4);
auto f3_3 = std::bind(&FC::data_2, &function_3);

f3_1(); 
f3_2();
f3_3();  // 返回 int

std::_Bind_helper<false, FC &, int, int, int, int>::type f3_1;
std::_Bind_helper<false, int(FC::*)(int, int, int, int) const, int, int, int, int>::type f3_2;
std::_Bind_helper<false, int FC::*, FC *>::type f3_3;
  1. 绑定lambda表达式
auto function_4 = [](int a, int b, int c, int d) {
        std::cout << "调用了 lambda 函数" << std::endl;
        std::cout << "传入参数:[" << a << ", " << b << ", " << c << ", " << d << "]" << std::endl;
        int sum = a + b + c + d;
        std::cout << "返回值:" << sum << std::endl;
        return sum;
};
auto f4 = std::bind(function_4, 1, 2, 3, 4);
f4();

std::_Bind_helper<false, lambda int(int a, int b, int c, int d)> &, int, int, int, int>::type f4;

用途

光像上面这样,其实就是一个用途,把一个 int(int, int, int, int) 对象转换成了 int() 对象,但是加上占位符,就更灵活了,这里只用一个例子来解释即可

// 不知道还记得前面 f1("乱传参"); 吗,这里不会报错,接下来说清楚一点

auto f1 = std::bind(function_1, 1, 2, 3, 4);  

任何 bind 函数后面的参数都是可变的,但是这样传进去固定了,就没意义了,因此可以用占位符来控制参数,std::placeholders::_1,占位符后面是_1,表示bind对象的第一个参数会使用到这里,看下面例子

int function_1(int a, int b, int c, int d);
auto f1 = std::bind(function_1, 1, 2, std::placeholders::_1, 4); 
// 说明了 f1 对象的第三个参数即 f1(1, 3, 5, 3,4); 中的5 被绑定到了 function_1的c上面
f1(1, "乱写", 5, 3); // 实际上 c就等于5了

// f1类型是
std::_Bind_helper<false, int(&)(int, int, int, int), int, int, const std::_Placeholder<1> &, int>::type f1;

有没有更灵活的,当然,没说一定要按顺序来的

int function_1(int a, int b, int c, int d);
auto f1 = std::bind(function_1, 1, 2, std::placeholders::_4, std::placeholders::_2);
f1(10, 20, 30, 40);  // 那么 d=20, c=40

// 也就是,占位符_数字是将bind对象对应的绑定的原函数对应位置
// 但是最大的占位符数字表示 f1()调用最少要传多少个参数,绑定位置不能传错参数,其他位置无所谓。

占位符

这样的占位符,提供了如下29个,显然不够可以自己定义:

  namespace placeholders
  {
    _GLIBCXX_PLACEHOLDER const _Placeholder<1> _1;
    _GLIBCXX_PLACEHOLDER const _Placeholder<2> _2;
    _GLIBCXX_PLACEHOLDER const _Placeholder<3> _3;
    _GLIBCXX_PLACEHOLDER const _Placeholder<4> _4;
    _GLIBCXX_PLACEHOLDER const _Placeholder<5> _5;
    _GLIBCXX_PLACEHOLDER const _Placeholder<6> _6;
    _GLIBCXX_PLACEHOLDER const _Placeholder<7> _7;
    _GLIBCXX_PLACEHOLDER const _Placeholder<8> _8;
    _GLIBCXX_PLACEHOLDER const _Placeholder<9> _9;
    _GLIBCXX_PLACEHOLDER const _Placeholder<10> _10;
    _GLIBCXX_PLACEHOLDER const _Placeholder<11> _11;
    _GLIBCXX_PLACEHOLDER const _Placeholder<12> _12;
    _GLIBCXX_PLACEHOLDER const _Placeholder<13> _13;
    _GLIBCXX_PLACEHOLDER const _Placeholder<14> _14;
    _GLIBCXX_PLACEHOLDER const _Placeholder<15> _15;
    _GLIBCXX_PLACEHOLDER const _Placeholder<16> _16;
    _GLIBCXX_PLACEHOLDER const _Placeholder<17> _17;
    _GLIBCXX_PLACEHOLDER const _Placeholder<18> _18;
    _GLIBCXX_PLACEHOLDER const _Placeholder<19> _19;
    _GLIBCXX_PLACEHOLDER const _Placeholder<20> _20;
    _GLIBCXX_PLACEHOLDER const _Placeholder<21> _21;
    _GLIBCXX_PLACEHOLDER const _Placeholder<22> _22;
    _GLIBCXX_PLACEHOLDER const _Placeholder<23> _23;
    _GLIBCXX_PLACEHOLDER const _Placeholder<24> _24;
    _GLIBCXX_PLACEHOLDER const _Placeholder<25> _25;
    _GLIBCXX_PLACEHOLDER const _Placeholder<26> _26;
    _GLIBCXX_PLACEHOLDER const _Placeholder<27> _27;
    _GLIBCXX_PLACEHOLDER const _Placeholder<28> _28;
    _GLIBCXX_PLACEHOLDER const _Placeholder<29> _29;
  }

结合bind和function

值得注意的是,四种类型不一样,不能相互赋值

f1 = f2; // 会报错,因为两者类型不一样

之前的定义中,都是用的auto关键字,但其实可以直接用function类来定义,这样就可以保证类型一样,能够赋值了

std::function<int(int)> f1_1 = std::bind(function_1, 1, std::placeholder::_1, 3, 4);
std::function<int(int)> f2_1 = std::bind(function_2, 1, 2, std::placeholder::_1, 4);
f1_1(10); // 执行 function_1 函数
f1_1 = f2_1;
f1_1(10);  // 执行 function_2 函数

有什么需要注意的呢?

std::function<int(int)> // 表示传入参数只有一个 int 型
std::bind(function_1, 1, std::placeholder::_1, 3, 4); // 只有一个占位符,因此前面的std::function中也只能有一个参数

f1_1(10); // 必须跟 function 保持一致

总结与用途

本文讲了bindfunction两个用法,function是一个函数包装器,bind是将一个可调用对象绑定参数后再返回一个函数包装器(function或者_Bind_helper),绑定参数时可以使用占位符,来改变最终函数包装器的调用方法(即参数个数可以不同)。

一个简单用法如下:

std::vector<std::function<void()>> fs;  // 定义一个可调用对象数组

// 定义两个函数
void function_1(int a, std::string b);
void function_2(std::vector<int>& a, float b);

// 把两个函数绑定到对象数组上面
fs.emplace_back(
    std::bind(function_1, 10, "hello")
);
fs.emplace_back(
    std::bind(function_2, vec, 3.14)
);

// 其他获得 fs 访问权的代码都可以调用这两个函数
fs[0]();
fs[1]();

// 在定义线程池中,这是一个很好的用法