std::function和std::bind简介

发布于 2021-09-19  130 次阅读


一、可调用对象

  可调用对象有如下几种定义:

  • 是一个函数指针,参考 C++ 函数指针和函数类型;
  • 是一个具有operator()成员函数的类的对象;
  • 可被转换成函数指针的类对象;
  • 一个类成员函数指针;

  C++中可调用对象的虽然都有一个比较统一的操作形式,但是定义方法五花八门,这样就导致使用统一的方式保存可调用对象或者传递可调用对象时,会十分繁琐。C++11中提供了std::function和std::bind统一了可调用对象的各种操作。

// 普通函数
int add(int a, int b)
{
    return a + b;
}

// lambda表达式
auto mod = [](int a, int b)
{
    return a + b;
};

// 函数对象类
struct minus
{
    int operator()(int a, int b)
    {
        return a - b;
    }
};

  上述三种可调用对象虽然类型不同,但是共享了一种调用形式:

int(int ,int)

  通过std::function可以将上述类型保存下来:

std::function<int(int, int)> addFunc = add;
std::function<int(int, int)> modFunc = mod;
std::function<int(int, int)> minusFunc = minus();

二 std::function

  • std::function 是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。
  • 定义格式:std::function<函数类型>。
  • std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。

三 std::bind

  它的主要功能就是作为函数适配器,它接受一个可调用对象,并生成一个新的可调用对象。同时,std::bind可以绑定参数(类似std::thread),并且可以使用占位符代替,其做法很类似于数据查询时所使用的使用占位符先解析。std::bind主要有以下两个作用:

  • 将可调用对象和其参数绑定成一个仿函数;
  • 只绑定部分参数,减少可调用对象传入的参数。

3.1 绑定普通函数

#include <iostream>
#include <functional>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    /* ===== 绑定默认参数 ===== */

    auto defaultAdd = std::bind(add, 5, 6);

    std::cout << defaultAdd() << std::endl;

    /* ===== 使用一个占位符 ===== */

    auto placeholderAdd = std::bind(add, std::placeholders::_1, 6);

    std::cout << placeholderAdd(5) << std::endl;

    return 0;
}

  与std::thread相似,第一个参数是函数名,普通函数做实参时会隐式转换为函数指针。因此:

auto defaultAdd = std::bind(add, 5, 6);
// equal
auto defaultAdd = std::bind(&add, 5, 6);

3.2 绑定成员函数

#include <iostream>
#include <functional>

class Cal
{
public:
    int add(const int& a, const int& b)
    {
        return a + b;
    }
};

int main()
{
    Cal calculator;
    auto callBind = std::bind(&Cal::add, &calculator, std::placeholders::_1, std::placeholders::_2);

    // auto callBind = std::bind(&Cal::add, calculator, std::placeholders::_1, std::placeholders::_2);

    // auto callBind = std::bind(&Cal::add, Cal(), std::placeholders::_1, std::placeholders::_2);

    std::cout << callBind(5, 1) << std::endl;

    return 0;
}

  这里需要注意,注释部分也是可行的,对象以拷贝(Copied)的方式被传入std::bind对象。但是,对于不可以以拷贝方式传入的对象(std::unique_ptr一类)需要额外注意。
  下面说明上述代码中的bind参数:

  1. 第一参数为指向成员函数的指针。其可以使用&Fname,也可以使用std::mem_fn(&Fname)对象。
  2. 第二参数为成员函数指针所属的具体对象。

3.3 绑定引用参数

#include <iostream>
#include <functional>

int add(int a, int& b)
{
    return a + b++;
}

int main()
{
    int param{ 5 };

    auto addFunc = std::bind(&add, 0, std::ref(param));

    addFunc();

    std::cout << param << std::endl;

    return 0;
}

  千言万语汇成一句:使用std::ref,而不是直接传入对象。

3.4 附言

  不要尝试用std::bind去绑定std::unique_ptr,其实是我不会。