前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Example】C++ 回调函数及 std::function 与 std::bind

【Example】C++ 回调函数及 std::function 与 std::bind

原创
作者头像
芯片烤电池
修改2022-04-28 10:07:17
4.4K0
修改2022-04-28 10:07:17
举报
文章被收录于专栏:C++教程

回调函数是做为参数传递的一种函数,在早期C样式编程当中,回调函数必须依赖函数指针来实现。

而后的C++语言当中,又引入了 std::function 与 std::bind 来配合进行回调函数实现。

标准库中有大量函数应用到了回调函数,其中 std::sort 就是一个经典例子。

一,回调函数

回调函数的创建步骤大概为:

1,声明一个函数指针类型。

2,拟写使用回调函数的函数,将函数指针类型及变量名声明作为参数传递。

3,拟写符合函数指针类型的实现函数,将实现函数的指针作为参数传递给使用它的函数。

下面演示了一个最简单的回调函数定义及使用:

代码语言:javascript
复制
typedef int (*Calc)(int a, int b);
int CalcValue(int a, int b, const Calc &func) {
    return func(a, b);
}

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

int main()
{
    int a = 4;
    int b = 6;
    int c = CalcValue(a, b, Add);

    std::cout << "Value: " << c << std::endl;
    return EXIT_SUCCESS;
}

可以看到,我们通过语法:

代码语言:javascript
复制
typedef int (*Calc)(int a, int b);

来定义了回调函数的指针类型,包括返回值类型、(*类型名)函数指针、参数表。

继而又定义并且实现了回调函数的使用者函数:

代码语言:javascript
复制
int CalcValue(int a, int b, const Calc &func) {
    return func(a, b);
}

再去定义并实现符合函数指针类型的实现函数:

代码语言:javascript
复制
int Add(int a, int b) {
    return a + b;
}

必须要注意的是,实现函数的类型必须要和函数指针的类型声明一致,也就是返回值和参数表(个数、类型)要完全一致。

这样就完成了一个简单且最基本的回调函数。

那么,回调函数是什么情况下使用的呢?

举个最经典的例子就是 std::sort,当你需要给一个存储有自定义结构体的 vector 进行排序时,编译器是无法知道如何对自定义结构体进行排序的。

这时候就需要实现一个回调函数来告诉编译器如何排序:

代码语言:javascript
复制
typedef struct DataPool {
    int value = 0;
    int date = 0;
    struct DataPool(int v, int d) : value(v), date(d) {};
}DataPool;

bool SortCallBack(const DataPool &a, const DataPool& b) {
    return a.value < b.value;
};

int main()
{
    vector<DataPool> vec;
    vec.push_back(DataPool(2, 1));
    vec.push_back(DataPool(5, 2));
    vec.push_back(DataPool(1, 3));

    std::sort(vec.begin(), vec.end(), SortCallBack);
    return EXIT_SUCCESS;
}

这样,就相当于自定义了 struct 的排序规则,自然编译器也可以使用 std::sort 对自定义 struct 进行排序操作。

二、std::function 与 std::bind

上面演示了最简单的回调函数创建及使用,然而,上面的代码却出现了一个局限性,就是:

如果需要去回调一个类成员函数,函数指针则无法指向类成员函数

在基本C样式面向过程编程当中,这种局限性并不那么明显甚至可以说不存在。但是到了C++当中,这种弊端就显而易见了,解决方式便是使用 std::function 与 std::bind 互相配合。

它们的头文件是:

代码语言:javascript
复制
#include <functional>

std::function

std::function 是一个模板类。作用是对C++中的可调用对象进行包装,例如普通函数、成员函数、模板函数、静态函数、lambda表达式等。

它的最基本的作用是,简化调用的复杂程度,统一调用的方式。如果代码中混杂着大量普通函数、模板函数、lambda,使用 std::function 是非常有必要的。

语法是:

代码语言:javascript
复制
【伪代码】std::function<returnType(argType, argType,...)> func;

【常规情况】std::function<int(int, int)> func;

可以看到,这个模板类当中对类型的声明方式是 < 返回值类型 ( 参数类型1, 参数类型2, ...) >。

你几乎可以拿它包装任何可调用对象,只需简单粗暴的将可调用对象作为右值赋值给它:

代码语言:javascript
复制
bool CompareInt(int a, int b) {
    return a > b;
}

std::function<bool(int, int)> compareFunc = CompareInt;

那么如何使用它来调用类成员函数呢?这时就需要用到经常与 std::function 配合使用的 std::bind。

std::bind

它是一个基于模板的函数,顾明思意它的作用是绑定并返回一个 std::function 对象。

那么什么是“绑定”?它本身作为延迟计算的思想的一种实现,作为一个调用过程当中的转发者而存在,返回一个 std::function 对象

它与 std::function 不同的是,function 是模板类,bind 是模板函数,而 bind 返回的可调用对象可以直接给 function 进行包装并保存

为什么要进行“包装”与“转发”呢?

首先,不规范的解释是,function 的作用是包装,它可以包装类成员函数,但却无法生成类成员函数的可调用对象。而 std::bind 则是可以生成。

因此,function 与 bind 结合后,便成为了 C++ 中类成员函数作为回调函数的一种规范的实现方式。

std::bind 的语法是:

代码语言:javascript
复制
int Add(int a, int b) {
    return a + b;
}

/* --- 普通函数 --- */
【伪代码】std::bind(&funcName, std::placeholders::_1, ...);

【常规情况】std::bind(&Add, std::placeholders::_1, std::placeholders::_2);

/* --- 类成员函数 --- */
【伪代码】std::bind(&className::funcName, classPtr, std::placeholders::_1, ...);

【常规情况】std::bind(&BrainToolBox::Add, brain, std::placeholders::_1, std::placeholders::_2);

当用作普通函数的绑定时,第一个参数是可调用对象(普通函数、lambda等),而第二个参数开始对应可调用对象的参数表。

std::placeholders::_1 代表可调用对象的第一个参数,_2就代表第二个参数,依此类推。

当用作类成员函数的绑定时,第一个参数仍然是作为类成员的可调用对象引用,第二个参数则是对象的指针,而第三个参数开始对应可调用对象的参数表。

同样使用 std::placeholders::_* 依次向后推。

所以,与 std::function 相结合,便可以实现对类成员函数的调用:

代码语言:javascript
复制
class BrainToolBox
{
public:
    int Add(int a, int b) { return a + b; };
};

int main()
{
    int a = 4;
    int b = 6;

    std::shared_ptr<BrainToolBox> brain = std::make_shared<BrainToolBox>();
    std::function<int(int, int)> addFunc = std::bind(&BrainToolBox::Add, brain, std::placeholders::_1, std::placeholders::_2);
    
   int c = addFunc(a, b);
    std::cout << "c Value: " << c << std::endl;

    return EXIT_SUCCESS;
}

对 std::bind 的额外注解:

1,调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针(可以包含智能指针,如 std::shared_ptr 与 std::unique_ptr),指向将访问其成员的对象。 2,到 bind 的参数被复制或移动,而且决不按引用传递,除非包装于 std::ref 或 std::cref 。 3,允许同一 bind 表达式中的多重占位符(例如多个 _1 ),但结果仅若对应参数( u1 )是左值或不可移动右值才良好定义。  --- CPP Reference

====================================

芯片烤电池 C++ Example 2022-Spring Season Pass :

【Example】C++ 标准库常用容器全面概述

【Example】C++ 回调函数及 std::function 与 std::bind

【Example】C++ 运算符重载

【Example】C++ 标准库智能指针 unique_ptr 与 shared_ptr

【Example】C++ 接口(抽象类)概念讲解及例子演示

【Example】C++ 虚基类与虚继承 (菱形继承问题)

【Example】C++ Template (模板)概念讲解及编译避坑

【Example】C++ 标准库 std::thread 与 std::mutex

【Example】C++ 标准库多线程同步及数据共享 (std::future 与 std::promise)

【Example】C++ 标准库 std::condition_variable

【Example】C++ 用于编译时封装的 Pimpl 演示 (编译防火墙 Private-IMPL)

【Example】C++ 单例模式 演示代码 (被动模式、兼容VS2022编译)

====================================

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一,回调函数
  • 二、std::function 与 std::bind
    • std::function
      • std::bind
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档