当前位置: 首页 > news >正文

[C++11] 包装器 : function 与 bind 的原理及使用


文章目录

  • `function`
    • `std::function` 的基本语法
    • 使用 `std::function` 包装不同的可调用对象
    • `function`包装普通成员函数为什么要传入 `this` 指针参数?
      • 传入对象指针与传入对象实例的区别
    • 例题 :150. 逆波兰表达式求值 - ⼒扣(LeetCode)
  • `bind`
    • `std::bind` 的基本语法
    • `std::bind` 参数的顺序调整与绑定
      • 顺序调整
      • 参数的绑定
  • `std::function` 和 `std::bind` 的实际应用
      • 结论

function

std::function 是⼀个类模板,也是一个通用的、多态函数包装器,用于存储可调用对象。函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同,<font style="color:rgb(31,35,41);">std::function</font>的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型。

<font style="color:rgb(31,35,41);">std::function</font> 的实例对象可以包装存储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调⽤对象被称为 std::function 的⽬标。若 std::function 不含⽬标,则称它空。调空则抛出 std::bad_function_call 异常。


<font style="color:rgb(31,35,41);">function</font>被定义<font style="color:rgb(31,35,41);"><functional></font>头⽂件中:

std::function 的基本语法

#include <functional>template <class T>
class function; // 未定义的模板类template <class Ret, class... Args>
class function<Ret(Args...)>; // 以返回类型和参数类型列表定义模板
function<返回类型(可调用对象的参数类型1,参数类型2...)>  对象 = 可调用对象;
// int add(int a, int b)
// function<int(int, int)> func1 = add;

使用 std::function 包装不同的可调用对象

以下示例展示了 std::function 包装普通函数、仿函数、lambda 表达式、类静态成员函数和普通成员函数的用法。

#include<functional>
#include<iostream>
using namespace std;int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator() (int a, int b){return a + b;}};class Plus
{
public:Plus(int n = 10):_n(n){}static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return (a + b) * _n;}private:int _n = 0;
};int main()
{// 包装各种可调⽤对象function<int(int, int)> f1 = f; // 普通函数function<int(int, int)> f2 = Functor(); // 仿函数function<int(int, int)> f3 = [](int a, int b) {return a + b; }; // lambdacout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;// 包装静态成员函数// 成员函数要指定类域并且前⾯加&才能获取地址function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装普通成员函数// 普通成员函数还有⼀个隐含的 this 指针参数,所以绑定时传对象或者对象的指针过去都可以function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;return 0;
}

在C++中,普通成员函数的调用与静态成员函数或普通的非成员函数不同,因为它隐含了一个 this 指针参数。这是由于普通成员函数总是绑定到某个对象实例,因此在调用时需要知道具体是哪个对象调用了该函数。

function包装普通成员函数为什么要传入 this 指针参数?

当我们使用 std::function 来包装普通成员函数时,普通成员函数的签名实际上是:

ReturnType (ClassType::*)(ParamTypes...)

这个签名表示该成员函数属于特定的类,因此它并不完全等同于普通函数。每个普通成员函数的调用实际上是通过一个特定的对象调用的,而对象的地址(this 指针)在函数调用时必须传入。

在普通成员函数的调用中:

  • this 指针作为隐式参数,指向调用函数的对象实例。
  • std::function 包装这种成员函数时需要显式地传入 this 指针,以便知道调用时该成员函数应该作用于哪个对象实例。

例如,假设有如下成员函数:

double Plus::plusd(double a, double b) {return a + b;
}

在使用 std::function 包装时,由于 plusd 是非静态成员函数,需要显式传入一个 Plus 实例(对象)或该实例的指针作为 this。可以通过传入对象指针 Plus*,或者直接传递一个对象实例 Plus 来间接实现这种绑定。

传入对象指针与传入对象实例的区别

  1. 传入对象指针(例如 Plus*:这种情况下,std::function 会调用成员函数时使用传入的指针来绑定 this。先创建一个Plus实例,然后传入该实例的地址。
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
f5(&pd, 1.1, 1.1);

在这里,f5(&pd, 1.1, 1.1); 调用时,&pd 指向的对象作为 this 指针传入。

  1. 传入对象实例(例如 Plus:当传入一个对象时,C++ 会复制这个对象并为其分配一个独立的内存空间,然后将其临时地址传给 this,使得 this 指向该副本。
function<double(Plus, double, double)> f6 = &Plus::plusd;
Plus pd;
f6(pd, 1.1, 1.1);

这样,调用 f6(pd, 1.1, 1.1); 会将对象 pd 复制一份传入,使得成员函数 plusdthis 指针指向该副本。

传入对象实例的优缺点:

  • 优点:传入对象实例更加直观,代码上不需要关注指针。
  • 缺点:会产生对象的拷贝(除非对象使用 std::move),因此可能有额外的开销。如果对象较大或者包含较多成员变量,拷贝代价较高。
  1. **直接传入一个匿名对象:**减少拷贝次数
function<double(Plus, double, double)> f6 = &Plus::plusd;
f6(Plus(), 1.1, 1.1);

当我们使用 Plus() 作为匿名对象传入时:

  • 匿名对象在调用的那一行直接生成,不需要从其他地方复制数据。
  • std::function 中的 f6 会直接使用这个匿名对象,临时对象的生命周期刚好覆盖整个调用过程,调用结束后立即销毁。

这样做可以在保证代码清晰的同时避免多余的拷贝。所以,传入 Plus() 是一种优化写法,尤其适合对象初始化开销较大、但不需要持续存在的情况。

所以在包装匿名对象时一般推荐使用该种方法。

例题 :150. 逆波兰表达式求值 - ⼒扣(LeetCode)

. - 力扣(LeetCode)

// 使⽤map映射string和function的⽅式实现
// 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;// Fixing the map initializationmap<string, function<int(int, int)>> opFuncMap = {{"+", [](int x, int y){ return x + y; }},{"-", [](int x, int y){ return x - y; }},{"*", [](int x, int y){ return x * y; }},{"/", [](int x, int y){ return x / y; }}};for(auto str : tokens){if(opFuncMap.count(str)){int top = st.top();st.pop();int next_top = st.top();st.pop();int ret = opFuncMap[str](next_top, top);st.push(ret);}else // Handling operand case{st.push(stoi(str));}}return st.top();}
};

<font style="color:rgb(31,35,41);">bind</font>

bind<functional>头文件中,std::bindfunction类似,也是⼀个函数模板,同时是一个函数适配器,用于将可调用对象的参数进行绑定或者参数顺序的调整,返回一个新的可调用对象(本质是一个仿函数对象)。

std::bind 可以调整原有函数的参数个数和顺序,适配更为灵活的调用方式。它广泛用于实现函数的“占位符”特性和简化代码的参数传递。

#include <functional>template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

std::bind 的基本语法

auto newCallable = bind(callable,arg_list); 
// newCallable 为绑定后的可调用对象 类型由 auto 推导
// callable 是要进行绑定或者进行调整参数传递顺序的函数
// arg_list 是 callable 进行具体调整的参数列表(可以包括占位符或绑死的参数)

其中<font style="color:rgb(31,35,41);">newCallable</font>本⾝是⼀个可调⽤对象,<font style="color:rgb(31,35,41);">arg_list</font>是⼀个逗号分隔的参数列表,对应给定的<font style="color:rgb(31,35,41);">callable</font>的参数。当我们调⽤<font style="color:rgb(31,35,41);">newCallable</font>时,<font style="color:rgb(31,35,41);">newCallable</font>会调⽤<font style="color:rgb(31,35,41);">callable</font>,并传给它<font style="color:rgb(31,35,41);">arg_list</font>中的参数。

std::bind 参数的顺序调整与绑定

顺序调整

std::bind 中,通过 placeholders 命名空间可以使用 _1_2 等占位符表示绑定的函数参数。

using namespace placeholders; // 将占位符全部展开

这些占位符用于定义生成的可调用对象中参数的位置,例如 _1 表示第一个参数, _2 表示第二个参数,以此类推。

using placeholders::_1;
using placeholders::_2;
// using placeholders::_3;int Sub(int a, int b)
{return (a - b) * 10;
}auto sub1 = bind(Sub, _1, _2); 
// 传入Sub函数,_1 _2表示使用新的可调用对象sub1时传入的第一个和第二个参数
cout << sub1(10, 5) << endl; // Sub(10, 5);auto sub2 = bind(Sub, _2, _1); 
// 传入Sub函数,_1 _2表示使用新的可调用对象sub1时传入的第二个和第一个参数
cout << sub2(10, 5) << endl; // Sub(5, 10);

参数的绑定

如果想让某个参数的值进行绑定,就在该参数位置上传入值即可,之后如果有传入参数需要可以继续按照占位符当前个数继续进行填写。 **_1, _2 ...**仅表示绑定后的新可调用对象传入的参数及顺序。

// 调整参数个数 (常⽤)
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;// 分别绑死第1 2 3个参数
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5, 1) << endl;
auto sub6 = bind(SubX, _1, 100, _2);
cout << sub6(5, 1) << endl;
auto sub7 = bind(SubX, _1, _2, 100);
cout << sub7(5, 1) << endl;// function<返回值类型(传入的各个参数类型 ,...)>
// 成员函数对象进⾏绑死,就不需要每次都传递了
function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << f6(move(pd), 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;// 可以利用 bind 绑死function需要包装的函数,将Plus::plusd函数包装后要传入的this部分绑死
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;

std::functionstd::bind 的实际应用

  1. 函数指针和回调函数

std::functionstd::bind 的组合可以让回调函数的参数更具灵活性。例如,在实现事件回调时可以使用 std::function 存储回调函数,并用 std::bind 将具体参数与回调绑定。

  1. 函数作为容器的元素

在需要存储不同类型的可调用对象的容器中,使用 std::function 是一个最佳选择。利用 std::function 可以将不同类型的函数包装在一个容器中统一存储,并在需要时调用。

  1. 参数绑定和延迟调用

std::bind 可以用于创建参数部分固定的函数对象,从而减少函数调用时的参数传递。这种方式在处理回调和异步编程中非常有用。


结论

C++11 提供的 std::functionstd::bind 为现代 C++ 编程带来了极大的便利。std::function 允许将不同类型的可调用对象进行统一存储和操作,简化了代码结构。而 std::bind 则可以灵活地调整函数参数和调用方式,为开发者提供了高效、简洁的代码编写方式。在日常开发中,合理运用这两个包装器可以显著提高代码的可读性和可维护性。

相关文章:

[C++11] 包装器 : function 与 bind 的原理及使用

文章目录 functionstd::function 的基本语法使用 std::function 包装不同的可调用对象function包装普通成员函数为什么要传入 this 指针参数&#xff1f;传入对象指针与传入对象实例的区别 例题 &#xff1a;150. 逆波兰表达式求值 - ⼒扣&#xff08;LeetCode&#xff09; bin…...

java项目-jenkins任务的创建和执行

参考内容: jenkins的安装部署以及全局配置 1.编译任务的general 2.源码管理 3.构建里编译打包然后copy复制jar包到运行服务器的路径 clean install -DskipTests -Pdev 中的-Pdev这个参数用于激活 Maven 项目中的特定构建配置&#xff08;Profile&#xff09; 在 pom.xml 文件…...

单片机中的BootLoader(重要的概念讲解)

文章目录 一、链接地址和执行地址1. 链接地址(Load Address)2. 执行地址(Execution Address)链接地址与执行地址的关系实际工作流程总结二、相对跳转和绝对跳转1. 相对跳转(Relative Jump)2. 绝对跳转(Absolute Jump)3. `BX` 和 `BL` 指令总结三、散列文件1. 散列文件的…...

【数据分享】中国食品工业年鉴(1984-2023) PDF

数据介绍 一、《中国食品工业年鉴》(以下简称《年鉴》)是一部全面反映上一年度全国食品工业发展情况纪年性、资料性、权威大型年刊。《年鉴(2023)》系统收录了全国食品行业各专业和 31个省(自治区、直辖市)2022年食品工业经济运行情况的综述&#xff0c;《年鉴》是由中国食品工…...

优选算法 - 1 ( 双指针 移动窗口 8000 字详解 )

一&#xff1a;双指针 1.1 移动零 题目链接&#xff1a;283.移动零 class Solution {public void moveZeroes(int[] nums) {for(int cur 0, dest -1 ; cur < nums.length ; cur){if(nums[cur] 0){}else{dest; // dest 先向后移动⼀位int tmp nums[cur];nums[cur] num…...

FairyGUI和Unity联动(入门篇)

一、FairyGUI编辑器中 1.新建按钮、新建组件 编辑器中界面简易设计如下 2.文件-发布设置-发布路径&#xff1a;自己unity项目Resources所在的路径 二、Unity 使用代码展示UI using FairyGUI; using System.Collections; using System.Collections.Generic; using UnityEngi…...

Go:文件输入输出以及json解析

文章目录 读取用户的输入文件读写读文件写文件 文件拷贝io包中接口的概念JSON 数据格式编码解码任意的数据&#xff1a; 读取用户的输入 从键盘和标准输入 os.Stdin 读取输入&#xff0c;最简单的办法是使用 fmt 包提供的 Scan… 和 Sscan… 开头的函数 看如下的程序 func t…...

编写红绿起爆线指标(附带源码下载)

编写需求&#xff1a; 想问问有没有能标注行情起爆点的指标。 效果展示&#xff1a; 红线上&#xff0c;出现绿柱转红柱做多。 蓝线下&#xff0c;出现红柱转绿柱做空。 源码展示&#xff08;部分源码&#xff0c;完整源码需下载源码文件&#xff09;&#xff1a; IsMainIn…...

设计模式(四)装饰器模式与命令模式

一、装饰器模式 1、意图 动态增加功能&#xff0c;相比于继承更加灵活 2、类图 Component(VisualComponent)&#xff1a;定义一个对象接口&#xff0c;可以给这些对象动态地添加职责。ConcreteComponent(TextView)&#xff1a;定义一个对象&#xff0c;可以给这个对象添加一…...

Android11 修改系统语言

1.定义一个view <RelativeLayoutandroid:id"id/rlChooseLanguage"style"style/SettingAboutItem"><TextViewstyle"style/SettingAboutItemTextView"android:text"string/choose_language" /><ImageView style"st…...

vue3 查看word pdf excel文件

也是在网上找的基础上修改的 可以直接使用 npm install vue-office/docx npm install vue-office/excel npm install vue-office/pdf​<template><divclass"Office-Preview"v-loading"loading"element-loading-text"文件加载中...">…...

java八股-垃圾回收机制-垃圾回收算法,分代回收,垃圾回收器

文章目录 垃圾回收算法引用计数法可达性分析算法 jvm垃圾回收算法标记清除算法标记整理算法复制算法本章总结 JVM中的分代回收本章总结 JVM有哪些垃圾回收器&#xff1f;1.串行垃圾收集器2.并行垃圾收集器3.CMS&#xff08;并发&#xff09;垃圾收集器本章小结 详细聊一下G1垃圾…...

iSCSI 和FC的概述

一、技术基础与架构 iSCSI 技术基础&#xff1a;iSCSI是基于TCP/IP协议的存储网络协议&#xff0c;它实现了在IP网络上运行SCSI协议。架构&#xff1a;iSCSI协议栈包括SCSI层、iSCSI层、TCP/IP层等&#xff0c;通过标准的以太网技术实现存储数据的传输。 FC 技术基础&#xff1…...

一文了解Android中的AudioFlinger

AudioFlinger 是 Android 音频框架中的一个核心组件&#xff0c;负责管理音频流的混合和输出。它是 Android 音频系统服务的一部分&#xff0c;作为音频框架和硬件之间的桥梁&#xff0c;接收应用程序的音频请求、进行混音处理&#xff0c;并最终通过音频硬件输出声音。 ![在这…...

超全面!一文带你快速入门HTML,CSS和JavaScript!

作为一名后端程序员&#xff0c;在开发过程中避免不了和前端打交道&#xff0c;所以就要了解一些前端的基础知识&#xff0c;比如三剑客HTML,CSS,JavaScript&#xff0c;甚至有必要学习一下Vue、React等前端主流框架。 学习文档&#xff1a;https://www.w3school.com.cn/ 一…...

C语言 | Leetcode C语言题解之第557题反转字符串中的单词III

题目&#xff1a; 题解&#xff1a; char* reverseWords(char* s) {int length strlen(s);char* ret (char*)malloc(sizeof(char) * (length 1));ret[length] 0;int i 0;while (i < length) {int start i;while (i < length && s[i] ! ) {i;}for (int p …...

408笔记合集

操作系统 《王道操作系统》-BitHachi 计算机网络 《王道计算机网络》--BitHachi 组成原理 《王道计算机组成原理》--BitHachi...

智慧医疗:纹理特征VS卷积特征

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…...

OPC学习笔记

一. 解决使用milo读取OPC设备字符串类型时&#xff0c;出现中文和特殊符号乱码的情况 解决前&#xff0c;读取字符串&#xff1a;你好 2. 解决后&#xff0c;读取字符串&#xff1a;你好 3. 解决前&#xff0c;读取字符串&#xff1a;165℃ 解决后&#xff0c;读取字符串&am…...

数据结构的时间复杂度和空间复杂度

目录 时间复杂度 空间复杂度 时间复杂度 基本操作的执行次数&#xff0c;为时间复杂度。 我们使用大O的渐进表示法来表示时间复杂度。 怎么使用&#xff1f; 先看例子&#xff1a; 在这个例子中&#xff0c; 基本操作为变量 count 的 加加 操作&#xff0c;并且&#xff0c;执行…...

HBase理论_背景特点及数据单元及与Hive对比

本文结合了个人的笔记以及工作中实践经验以及参考HBase官网&#xff0c;我尽可能把自己的知识点呈现出来&#xff0c;如果有误&#xff0c;还请指正。 1. HBase背景 HBase作为面向列的数据库运行在HDFS之上&#xff0c;HDFS缺乏随机读写操作&#xff0c;HBase正是为此而出现。…...

生产模式打包

在生产模式下打包 Node.js 和前端&#xff08;例如 Vue 或 React&#xff09;应用时&#xff0c;通常需要对代码进行优化&#xff0c;使其在生产环境中运行更高效。以下是如何在生产模式下配置和打包项目的步骤&#xff1a; 1. Node.js 生产模式打包 Node.js 本身不需要像前端…...

Vue的路由

Vue的路由 出发点&#xff1a;遇到多页面网页的反复跳转&#xff0c;有些繁琐&#xff0c;可以通过Vue的路由实现单页面中数据的变化 实现单页面中数据的变化&#xff08;通过Vue-router来进行操作的&#xff0c;数据的请求获取也需要ajax异步交互&#xff09;&#xff0c;具…...

Spring框架之策略模式 (Strategy Pattern)

策略模式&#xff08;Strategy Pattern&#xff09;详解 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;用于定义一系列算法&#xff0c;并将每种算法封装到独立的策略类中&#xff0c;使它们可以相互替换&#xff0c;从而使算法的变化独…...

探索Google Earth Engine:利用MODIS数据和R语言进行2000-2021年遥感生态指数(RSEI)的时空趋势分析

前段时间,小编学习了在GEE上进行遥感生态指数(RSEI)的评估,非常头疼,但是实验了两周后,亲测有效,主要采用的是MODIS数据分析了2000-2021年中国内蒙古某地的RSEI时间序列分布状况,现在把学习的代码分享给大家。 1 GEE计算RSEI 1.1研究区域导入与初步定义 var sa = ee…...

多商户中英双语电商系统设计与开发 PHP+mysql

随着全球电商市场的扩展&#xff0c;多商户平台成为了越来越多商家参与全球贸易的重要方式。为了适应不同语言用户的需求&#xff0c;尤其是中英双语用户的需求&#xff0c;设计一个支持中英双语的电商系统显得尤为重要。本文将重点探讨如何设计一个多商户中英双语电商系统&…...

牵手App红娘专属1V1服务,打造贴心交友指导

对于年轻一代而言&#xff0c;婚恋方式已明显区别于传统&#xff0c;他们更倾向于直接、活泼的交流方式&#xff0c;享受着在轻松愉快的氛围中边玩边交友的乐趣。线上社交平台&#xff0c;尤其是那些基于兴趣构建的交友模式&#xff0c;正逐渐成为他们探索爱情、寻找共鸣的新舞…...

论文解析:边缘计算网络中资源共享的分布式协议(2区)

目录 论文解析:边缘计算网络中资源共享的分布式协议(2区) 核心内容: 核心创新点的原理与理论: 多跳边缘计算场景 一、边缘计算的基本概念 二、多跳边缘计算场景的含义 三、多跳边缘计算场景的应用 四、多跳边缘计算场景的优势 论文解析:协作边缘计算网络中资源共…...

Android Osmdroid + 天地图 (一)

Osmdroid 天地图 前言正文一、配置build.gradle二、配置AndroidManifest.xml三、获取天地图的API Key① 获取开发版SHA1② 获取发布版SHA1 四、请求权限五、显示地图六、源码 前言 Osmdroid是一款完全开源的地图基本操作SDK&#xff0c;我们可以通过这个SDK去加一些地图API&am…...

浅谈:基于三维场景的视频融合方法

视频融合技术的出现可以追溯到 1996 年 , Paul Debevec等 提出了与视点相关的纹理混合方法 。 也就是说 &#xff0c; 现实的漫游效果不是从摄像机的角度来看 &#xff0c; 但其仍然存在很多困难 。基于三维场景的视频融合 &#xff0c; 因其直观等特效在视频监控等相关领域有着…...