C++ 新特性 | C++ 11 | function与bind

一、function与bind

1、什么是闭包?

在以往的c++程序中,回调一般都是用函数指针的方式来做,因为函数无法携带状态,这个其实是非常不方便的。可以使用闭包(closure) 来解决函数指针无状态问题。简单来说,闭包就是一个带状态的可执行体。它可以捕获函数需要的变量(值捕获或者引用捕获),然后在合适的时间来执行函数。通俗一点来说,一个函数,加上一些变量的组合,就是闭包。最能表示闭包含义的结构就是仿函数了,也就是重载了operator()的类。

在c++中,闭包(closure)的具体实现就是std::bind和lambda表达式,而function就是用来储存生成的闭包。

2、fucntion

2.1、什么是包装器?

std::function是一个函数包装器,该函数包装器模板能包装任何类型的可调用实体,如普通函数,函数对象,lamda表达式等。包装器可拷贝,移动等,并且包装器类型仅仅依赖于调用特征,而不依赖于可调用元素自身的类型。std::function是C++11的新特性,包含在头文件中。

2.2、使用

通过function对象来包装可调用实体,如下:

void func(int a)
{
	cout << a << endl;
}

struct Display {
    void operator()(int x){
        cout << x << endl;
    }
};

int main()
{
    // 创建function对象
    function<void(int)> callback;

    // 定义函数指针
    void (*pfnFunc)(int) = func;
    // 1、function对象实例包装函数指针
    callback = pfnFunc;
    callback(10);

    // 2、function包装函数
    callback = func;
    callback(10);

    // 3、function包装对象函数
    callback = Display();
    callback(10);

    // 4、function包装lamda表达式
    callback = [](int a) {cout << a << endl;};
    callback(10);

	return 0;
}

2.3、使用function解决模板低效性

通过下面的示例讲解通过function解决模板低效性,如下:

int func(int a) {
	return a;
}

struct Display {
    int operator()(int x){
        return x;
    }
};

template<typename Type, typename Func>
Type use_func(Type type, Func func) {
    static int num = 0;
    num ++;
    cout << "num = " << num << " addr: " << &num << endl;
    return func(type);
}

int main()
{
    int a = 20;

    use_func(a, func);
    use_func(a, Display());

	return 0;
}

输出结果

num = 1 addr: 0x47e254
num = 1 addr: 0x47e250

Process returned 0 (0x0)   execution time : 0.076 s
Press any key to continue.

在两次use_func函数调用中,模板参数Type都被设置成int,模板参数Func都接收一个int值并返回一个int值,好像Func的类型都相同,因此只会实例化模板一次。但是,根据实际的输出结果来看,use_func模板实例化了两次。use_func模板函数有一个静态成员num,可以根据它的地址来确认模板实例化了几次,通过输出结果来看有两个不同的地址,说明模板实例化了两次。

为了了解其中的原因,思考下编译器如何判断模板参数Func的类型,首先,来看函数调用use_func(a, func),其中的func是一个函数的名称,该函数接收int参数并返回int值,因此、模板参数Func的类型是int(*)int。use_func(a, Display())函数调用,第二个参数是Display对象,模板参数Func的类型是Display,此时会用Display类型再次实例化use_func模板。

通过function解决前面遇到的模板低效问题

注意到前面程序中的函数指针、函数对象有一个相同的地方,它们都接收一个int参数返回一个int结果,可以说它们的调用特征标是相同的。调用特征标是由函数返回值类型参数列表共同构成,因此,这两个模板实例的调用特征标都是int(int)

模板function是在头文件functional声明的,它从调用特征标的角度定义了一个对象,可用于包装调用相同特征标的函数指针、函数对象或lambda表达式。要减少上面程序实例化次数,可以使用function<int(int)>创建二个包装器,在对use_func的二次调用中,让Func的类型都相同(function<int(int)>),因此只实例化一次,如下:

int main()
{
    int a = 20;

    function<int(int)> func1 = func;
    function<int(int)> func2 = Display();


    use_func(a, func1);
    use_func(a, func2);

	return 0;
}

输出结果

num = 1 addr: 0x47f250
num = 2 addr: 0x47f250

Process returned 0 (0x0)   execution time : 0.007 s
Press any key to continue.

从输出结果来看,修改成使用function<int(int)>后,模板只实例化了一次

3、bind

3.1、定义

bind 是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。常用的使用场景如下:

  • 使用bind实现参数顺序的调整和将指定参数设置成固定值
  • function 与 bind 结合后,便成为了 C++ 中类成员函数作为回调函数的一种规范的实现方式

3.2、使用

场景一:使用bind指定参数设置成固定值

#include <iostream>
#include <functional>
using namespace std;

void foo(int a, int b, int c) {
    std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
}

int main() {
    auto func = std::bind(foo, 1, 2, std::placeholders::_1);
    func(3);  // 调用 func,实际上调用 foo(1, 2, 3),输出结果:a = 1, b = 2, c = 3

    return 0;
}

场景二:使用bind调整参数顺序

#include <iostream>
#include <functional>
using namespace std;

void display(int a, double b) {
    std::cout << "a = " << a << ", b = " << b << std::endl;
}

int main() {
    auto func = std::bind(display, std::placeholders::_2 ,std::placeholders::_1);

    int a = 20;
    double b = 30.5;
    func(b, a);  // 原始的display函数参数顺序是(int, double),使用bind后参数的顺序调整成(double, int),使用bind调整了参数顺序

    return 0;
}

场景三:类成员函数作为回调函数

#include <iostream>
#include <functional>
using namespace std;

typedef std::function<void(int)> GeneralCB;

// 创建 function 对象
template<typename T>
static GeneralCB CreateCB(void (T::*func)(int), T* object)
{
    if (object)
    {
        return std::bind(func, object, std::placeholders::_1);
    }
    return nullptr;
}

class Observer {
public:
    void Execute() {
        m_cb(20);
    }
public:
    GeneralCB m_cb;
};

class Subject {
public:
    Subject()
    {
        m_pObserver = new Observer;
        m_pObserver->m_cb = CreateCB(&Subject::Display, this);
    }
    void Display(int a) {
        cout << "a = " << a << endl;
    }
    void Execute() {
        m_pObserver->Execute();
    }
private:
    Observer *m_pObserver;
};

int main() {
    Subject obj;
    obj.Execute();
    return 0;
}

3.3、std::placeholders

占位符,c++11中有29个占位符,分别是_1~_29,一般情况下是std::placeholders::_1这样的。占位符的作用就是用来代表参数的,std::placeholders::_1表示的是std::bind创建的std::function对象被调用时,传入的第一个参数,而std::placeholders::_2则是第二个参数依次类推。

调整std::placeholders::_x在std::bind时的顺序,就可以起到调整参数顺序的作用了。此外,也可以在std::bind的时候不用std::placeholders::_x,而直接写成固定的值,这样子调用std::function存储的对象时,对应位置的参数将是固定值。

#include <iostream>
#include <functional>
using namespace std;

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

int main() {
    auto f1 = std::bind(Display, std::placeholders::_1, std::placeholders::_2);
    auto f2 = std::bind(Display, std::placeholders::_2, std::placeholders::_1);
    auto f3 = std::bind(Display, std::placeholders::_1, 100);

    f1(10, 20);  // 输出结果:a = 10, b = 20
    f2(10, 20);  // 输出结果:a = 20, b = 10
    f3(10, 20);  // 输出结果:a = 10, b = 100

    return 0;
}

3.4、std::bind 和 lambda表达式的一些区别

lambda底层的实现其实就是仿函数,而std::bind的实现,其实也是仿函数,和lambda不同的一个点是,lamdba的函数体是用户自己写的,而std::bind则是调用其他函数,所以std::bind就会比lambda多一个函数指针的大小。

#include <iostream>
#include <functional>
using namespace std;

void Func1(int32_t x)
{
    std::cout << x;
}

void Func2(int32_t x, int64_t y)
{
    std::cout << x << y;
}

int main()
{
    int32_t x;
    int64_t y;
    auto f1 = std::bind(Func1, x);
    auto f2 = std::bind(Func2, x, y);

    auto ff1 = [x]() { std::cout << x; };
    auto ff2 = [x, y]() { std::cout << x << y; };

    std::cout << sizeof(f1) << " " << sizeof(f2) << std::endl;    // 输出结果:8 24
    std::cout << sizeof(ff1) << " " << sizeof(ff2) << std::endl;  // 输出结果:4 16

    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值