c++ 回调函数,std::function,std::bind

回调函数

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

  1. 声明一个函数指针类型。
  2. 拟写使用回调函数的函数,将函数指针类型及变量名声明作为参数传递。
  3. 拟写符合函数指针类型的实现函数,将实现函数的指针作为参数传递给使用它的函数。

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

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;
}

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

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 << "c: " << c << std::endl;return 0;
}

std::function

可调用对象:

  • 一个函数指针
  • 一个具有operator()成员函数的类对象(传说中的仿函数),lambda表达式
  • 一个可被转换为函数指针的类对象
  • 一个类成员(函数)指针
  • bind表达式或其它函数对象

std::function 是一个模板类。作用是对C++中的可调用对象进行包装,例如普通函数、成员函数、模板函数、静态函数、lambda表达式等。可以把std::function看做一个函数对象,用于表示函数这个抽象概念。std::function的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function的目标,若std::function不含目标,则称它为空,调用空的std::function的目标会抛出std::bad_function_call异常。

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

std::function<returnType(argType, argType,...)> func;

模板类当中对类型的声明方式是 < 返回值类型 ( 参数类型1, 参数类型2, …) >

使用场景:

  1. 绑定一个函数(普通函数或者静态函数)
  2. 实现回调函数
  3. 作为函数入参
std::function<void(int)> f; // 这里表示function的对象f的参数是int,返回值是void
#include <functional>
#include <iostream>struct Foo {Foo(int num) : num_(num) {}void print_add(int i) const { std::cout << num_ + i << '\n'; }int num_;
};void print_num(int i) { std::cout << i << '\n'; }struct PrintNum {void operator()(int i) const { std::cout << i << '\n'; }
};int main() {// 存储自由函数std::function<void(int)> f_display = print_num;f_display(-9);// 存储 lambdastd::function<void()> f_display_42 = []() { print_num(42); };f_display_42();// 存储到 std::bind 调用的结果std::function<void()> f_display_31337 = std::bind(print_num, 31337);f_display_31337();// 存储到成员函数的调用std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;const Foo foo(314159);f_add_display(foo, 1);f_add_display(314159, 1);// 存储到数据成员访问器的调用std::function<int(Foo const&)> f_num = &Foo::num_;std::cout << "num_: " << f_num(foo) << '\n';// 存储到成员函数及对象的调用using std::placeholders::_1;std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);f_add_display2(2);// 存储到成员函数和对象指针的调用std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);f_add_display3(3);// 存储到函数对象的调用std::function<void(int)> f_display_obj = PrintNum();f_display_obj(18);
}
#include <functional>
class A
{std::function<void()> callback_;
public:A(const std::function<void()>& f) :callback_(f) {};void notify(void){callback_();}
};
class Foo {
public:void operator()(void){std::cout << __FUNCTION__ << std::endl;}
};
int main(void)
{Foo foo;A aa(foo);aa.notify();
}
#include <functional>
void call_when_even(int x, const std::function<void(int)>& f)
{if (!(x & 1)){f(x);}
}
void output(int x)
{std::cout << x << " ";
}
int main(void)
{for (int i = 0; i < 10; ++i){call_when_even(i, output);}std::cout << std::endl;
}

std::bind

是一个基于模板的函数,作用是绑定并返回一个 std::function 对象。
那么什么是“绑定”?它本身作为延迟计算的思想的一种实现,作为一个调用过程当中的转发者而存在,返回一个 std::function 对象。
它与 std::function 不同的是,function 是模板类,bind 是模板函数,而 bind 返回的可调用对象可以直接给 function 进行包装并保存。

为什么要进行“包装”与“转发”呢?
首先,不规范的解释是,function 的作用是包装,它可以包装类成员函数,但却无法生成类成员函数的可调用对象。而 std::bind 则是可以生成。
因此,function 与 bind 结合后,便成为了 C++ 中类成员函数作为回调函数的一种规范的实现方式。

std::bind(&funcName, std::placeholders::_1, ...);

当用作普通函数的绑定时,第一个参数是可调用对象(普通函数、lambda等),而第二个参数开始对应可调用对象的参数表。
std::placeholders::_1 代表可调用对象的第一个参数,_2就代表第二个参数,依此类推。

当用作类成员函数的绑定时,第一个参数仍然是作为类成员的可调用对象引用,第二个参数则是对象的指针,而第三个参数开始对应可调用对象的参数表。同样使用 std::placeholders::_* 依次向后推。

因为类成员函数都有一个默认的参数,this,作为第一个参数,这就导致了类成员函数不能直接赋值给std::function,需要std::bind,简言之,std::bind的作用就是转换函数签名,将缺少的参数补上,将多了的参数去掉,甚至还可以交换原来函数参数的位置。

注意

  1. 调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针(可以包含智能指针,如 std::shared_ptr 与std::unique_ptr),指向将访问其成员的对象。
  2. 到 bind 的参数被复制或移动,而且决不按引用传递,除非包装于 std::ref 或 std::cref 。
  3. 允许同一 bind 表达式中的多重占位符(例如多个 _1 ),但结果仅若对应参数( u1 )是左值或不可移动右值才良好定义。
class Baseclass
{
public:int Add(int a, int b) { return a + b; };
};
int main()
{int a = 1;int b = 2;std::shared_ptr<Baseclass> ptr_class = std::make_shared<Baseclass>();std::function<int(int, int)> addFunc = std::bind(&Baseclass::Add, ptr_class, std::placeholders::_1, std::placeholders::_2);int c = addFunc(a, b);std::cout << "c: " << c << std::endl;return 0;
}

参考
c++ 回调函数与std::function使用实例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/68778.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【CPP_Primer_Plus】学习助手

学习网站推荐 cppreference learncpp cplusplus tutorialspoint awesomecpp stackoverflow 视频课程推荐 码农论坛 cpp primer plus

Nginx基础+高级(2022版):待更新

1. 文章说明 说明&#xff1a;目前讲的是第一部分nginx核心技术篇&#xff0c;后需篇章会以第一部分为核心技术篇为基础来展开深度讲解&#xff0c;详情关注后续课程的发布。 2. 介绍和准备环境 2.1 介绍 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xf…

动态维护直径 || 动态维护树上路径 || 涉及LCA点转序列 || 对欧拉环游序用数据结构维护:1192B

https://www.luogu.com.cn/problem/CF1192B 对于直径的求法&#xff0c;常用dp或两次dfs&#xff0c;但如果要动态维护似乎都不太方面&#xff0c;那么可以维护树上路径最大值。 树上路径为&#xff1a; d e p u d e p v − 2 d e p l c a ( u , v ) dep_udep_v-2\times de…

iPhone 15 Pro展示设计:7项全新变化呈现

我们不应该再等iPhone 15 Pro在苹果9月12日的“Wonderlust”活动上发布了&#xff0c;而且可能会有很多升级。有传言称&#xff0c;iPhone 15 Pro将是自iPhone X以来最大的飞跃&#xff0c;这要归功于大量的新变化&#xff0c;从带有更薄边框的新钛框架到顶级A17仿生芯片和动作…

[管理与领导-70]:IT基层管理者 - 辅助技能 - 4- 职业发展规划 - 个人的能力盘点

目录 前言&#xff1a; 一、什么是能力&#xff08;What&#xff09; 1.1 什么是能力 1.2 能力类型 1.3 技能矩阵 二、优势与劣势模型 2.1 优势与劣势 2.2 SWOT模型 三、人才结构模型 3.1 广度优先&#xff1a;一字型/全面型人才 3.2 深度优先&#xff1a;I型人才、…

Django框架中使用drf框架开发

一、drf框架特点&#xff1a; 全称 Django REST framework 两大部分&#xff1a;序列化/反序列化 和 增删改查序列化&#xff1a;把数据库数据提取出来变成python常用格式的过程&#xff0c;例如转成json格式这种反序列化&#xff1a;把数据写入到数据库的过程&#xff0c…

浅谈一下酒吧和酒馆的不同

相信有很多朋友还不怎么清楚酒吧和酒馆的区别是什么&#xff0c;这里为大家简单介绍一下两者的不同&#xff0c;个人见解&#xff0c;如有错漏&#xff0c;欢迎指出。一、首先是他们的经营范围不同酒馆经营通常包含酒水和餐饮&#xff0c;适合朋友聚会或者是和商业伙伴聊天。而…

230903文本docx

处理文本 块级项目,每次文本超出右边界时都会添加一行.对段落,边界一般是页边距,但如果按列布局页,则也可是列边界,如果表格单元格内有段,则也可是单元格边界. 块级项属性指定其在页上的位置,如缩进项及段落前后间距.内联项属性一般指定显示内容的如字样,字体大小,粗体和斜体…

QLoRA:量化LLM的高效微调策略与实践

如果你对这篇文章感兴趣&#xff0c;而且你想要了解更多关于AI领域的实战技巧&#xff0c;可以关注「技术狂潮AI」公众号。在这里&#xff0c;你可以看到最新最热的AIGC领域的干货文章和案例实战教程。 一、前言 在大型语言模型&#xff08;LLM&#xff09;领域&#xff0c;微…

JavaScript单例模式

JavaScript单例模式 1 什么是单例模式2 实现一个基础的单例模式3 透明的单例模式4 用代理实现单例模式5 JavaScript 中的单例模式6 惰性单例 1 什么是单例模式 保证一个类只有一个实例&#xff0c;并提供一个访问它的全局访问点&#xff0c;这就是单例模式。 单例模式是一种常…

(位运算) 剑指 Offer 15. 二进制中1的个数 ——【Leetcode每日一题】

❓ 剑指 Offer 15. 二进制中1的个数 难度&#xff1a;简单 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为 汉明重量).&#xff09;。 提示&#xff…

计算机网络自顶向下-web页面请求历程

1. 准备: DHCP、 UDP、 IP 和以太网 假定 Bob 启动他的便携机&#xff0c;然后将其用一根以太网电缆连接到学校的以太网交换机 &#xff0c; 交换机与学校的路由器相连。学校的路由器与一个 ISP 连接&#xff0c; 本例中 ISP 为 comcast.net &#xff0c;为学校提供了 DNS 服务…

开发前期准备工作

开发前期准备工作 文章目录 开发前期准备工作0 代码规范0.1 强制0.2 推荐0.3 参考dao&#xff1a;跟数据库打交道service&#xff1a;业务层&#xff0c;人类思维解决controller&#xff1a;抽象化 0.4 注释规范0.5 日志规范0.6 专有名词0.7 控制层统一异常统一结构体控制层提示…

java线程和go协程

一、线程的实现 线程的实现方式主要有三种&#xff1a;内核线程实现、用户线程实现、用户线程加轻量级进程混合实现。因为自己只对java的线程比较熟悉一点&#xff0c;所以主要针对java线程和go的协程之间进行一个对比。 线程模型主要有三种&#xff1a;1、内核级别线程&#…

Unittest自动化测试框架vs Pytest自动化测试框架

引言   前面一篇文章Python单元测试框架介绍已经介绍了python单元测试框架&#xff0c;大家平时经常使用的是unittest&#xff0c;因为它比较基础&#xff0c;并且可以进行二次开发&#xff0c;如果你的开发水平很高&#xff0c;集成开发自动化测试平台也是可以的。而这篇文章…

如何培养潜在客户?看完这篇你就懂了

图片来源于&#xff1a;SaleSmartly官网 有效的潜在客户培育策略将帮助您将更多潜在客户转化为付费客户。 但是&#xff0c;这并不总是那么容易——您必须与其他公司争夺受众的注意力&#xff0c;并向您的领导证明为什么值得投资您的产品或服务。在本文中&#xff0c;我将向您展…

docker安装在linux下的docker安装操作步骤完整版

参考文档&#xff1a;http://wed.xjx100.cn/news/151901.html?actiononClick 第一步&#xff0c;卸载历史版本。这一步是可选的&#xff0c;如果之前安装过旧版本的Docker&#xff0c;可以使用如下命令进行卸载&#xff1a; yum remove docker \docker-client \docker-client…

STM32 RTC实验

RTC时钟简介 STM32F103的实时时钟&#xff08;RTC&#xff09;是一个独立的定时器。 STM32的RTC模块拥有一组连续计数的计数器&#xff0c;在相对应的软件配置下&#xff0c;可提供时钟日历的功能。 修改计数器的值可以重新设置系统的当前时间和日期。 RTC模块和时钟配置系统…

redis 报错 Redis protected-mode 配置文件没有真正启动

(error) DENIED Redis is running in protected mode because protected mode is enabled Redis protected-mode 是3.2 之后加入的新特性&#xff0c;在Redis.conf的注释中&#xff0c;我们可以了解到&#xff0c;他的具体作用和启用条件 链接redis 时只能通过本地localhost …

iKeyPrime完美解4G信号,可以登录iCloud,有消息通知,支持最新iOS16.6。

iKeyPrime是一款绕过激活锁界面的解锁工具&#xff0c;可以激活所有iPhone苹果手机&#xff0c;二网/三网恢复信号&#xff0c;并且支持插卡接打电话、收发短信、4G流量上网&#xff0c;支持iCloud登录&#xff0c;有消息通知&#xff0c;支持iPhone5S~X的所有型号&#xff0c;…