Modern C++ std::bind的实现原理

1. 前言

前面写过《std::function从实践到原理》,管中规豹了std::function的一点点原理,不过还有一个与std::function密切相关的函数std::bind, 允许编程者绑定几个参数,本文着重介绍它的实现原理。不介绍一下它,有点吃肉不吃蒜味道少一半的感觉。

2. overview

在陷入繁琐的实现原理之前,我们先打印一下bind对象,看看它都有什么成员变量,这样好做到有个大局观,方便小细节的理解。
一个简单的程序:

#include <functional>
#include <iostream>// A function taking three arguments
void printValues(int a, double b, const std::string& str) {std::cout << "Values: " << a << ", " << b << ", " << str << std::endl;
}int main() {// Using std::bind to bind values to the functionauto boundFunction3 = std::bind(printValues, 42, 3.14, "Hello");std::cout<<"sizeof(boundFunction3):"<<sizeof(boundFunction3)<<std::endl;// Invoking the bound functionboundFunction3();  // Output: Values: 42, 3.14, Helloauto boundFunction1 = std::bind(printValues, 42, std::placeholders::_1, std::placeholders::_2);std::cout<<"sizeof(boundFunction1):"<<sizeof(boundFunction1)<<std::endl;// Invoking the bound functionboundFunction1(3.14,"hello");  // Output: Values: 42, 3.14, Helloreturn 0;
}

在这里插入图片描述
可以看到

  1. std::bind返回的是一个std::_Bind对象,继承了std::_Weak_result_type
  2. 有两个成员对象:_M_f大概是指向原始函数的指针,_M_bound_args是一个tuple包含了3个元素(element)。
    在这里插入图片描述

看了成员以后,我们就可以大概猜出_Bind对象大小了。
请添加图片描述
boundFunction3是32个字节,boundFunction1是16个字节。你猜对了吗?

3. 实现细节 – _Bind对象怎么构建

有了上面的概览,应该很容易想到在调用std::bind时做了两件事:

  1. 把函数存入_Bind对象的成员_M_f中。
  2. 把bind函数后面的参数存入_Bind对象的tuple内,包括_1, _2等。

因为16行代码具有一般性(即有真参数,又有以后要绑定的参数_1_2), 我们直接按s调试这一行, gdb带我们来到了std::bind函数的定义:
在这里插入图片描述
__f承接了原始函数,在我们的例子中就是printValues;而__args是变参,承接了我们传进来的其余参数:
42, std::placeholders::_1, std::placeholders::_2

这些参数被转送给了__helper_type::type即_Bind类,它的构造函数如下:
在这里插入图片描述
很简单,只是给两个成员变量赋了值:

  1. _M_f: 指针,指向原始函数

  2. _M_bound_args: tuple类型,由传过来的42,_1,_2初始化

构造完毕.

4. 实现细节 – _Bind对象调用

4.1 想象实现

可以展开想象,_M_bound_args中有原始的参数列表,即42,_1,_2,还有原始的函数指针_M_f, 这些信息足够实现新函数的调用了。大概就是:
_M_f(_M_bound_args第一项,_M_bound_args第二项,_M_bound_args第三项),如果某一项是_n的形式,再用新函数中的第n项参数替换,在我们的例子中就是:
_M_f(42, _1被换成3.14, _2被替换成“hello”).

下面我们看看代码是不是如此?

4.2 _Bind重载operator()()

19          boundFunction1(3.14,"hello");

第19行step into.

479             _Result
480             operator()(_Args&&... __args)
481             {
482               return this->__call<_Result>(
483                   std::forward_as_tuple(std::forward<_Args>(__args)...),
484                   _Bound_indexes());
485             }

显然,_Bind对象能被调用,必须重载了operator()()才行。传给了__call两个参数, 一个字面意思就是把传进来的参数3.14,"hello"打包成一个tuple, 另一个参数是_Bound_indexes()有点不明所以。
让我们先看看_Bound_indexes是什么?

385    template<typename _Functor, typename... _Bound_args>386     class _Bind<_Functor(_Bound_args...)>387     : public _Weak_result_type<_Functor>388     {389       typedef typename _Build_index_tuple<sizeof...(_Bound_args)>::__type390     _Bound_indexes;

而_Build_index_tuple是这样定义的:

297   // Builds an _Index_tuple<0, 1, 2, ..., _Num-1>.
298   template<size_t _Num>
299     struct _Build_index_tuple
300     {
301 #if _GLIBCXX_USE_MAKE_INTEGER_SEQ
302       template<typename, size_t... _Indices>
303         using _IdxTuple = _Index_tuple<_Indices...>;
304
305       using __type = __make_integer_seq<_IdxTuple, size_t, _Num>;
306 #else
307       using __type = _Index_tuple<__integer_pack(_Num)...>;
308 #endif
309     };

即_Bound_indexes是_Index_tuple<0, 1, 2>, 因为sizeof…(_Bound_args)的值是3. (_Bound_args对应int, std::_Placeholder<1>, std::_Placeholder<2>)
故传给this->__call的两个参数大概是这样:

  1. std::tuple(3.14, “hello”)
  2. _Index_tuple<0,1,2>类型的一个对象

这一点也可以通过打印boundFunction1的类型来进一步验证:
在这里插入图片描述

4.3 占位符与新参数的转换

4.3.1 幽灵_Mu

上面有点扯远了,让我们回到__call函数,看看它的定义:

395       // Call unqualified396       template<typename _Result, typename... _Args, std::size_t... _Indexes>397     _Result398     __call(tuple<_Args...>&& __args, _Index_tuple<_Indexes...>)399     {400       return std::__invoke(_M_f,401           _Mu<_Bound_args>()(std::get<_Indexes>(_M_bound_args), __args)...402           );403     }

这里又出现了没见过的模板类:_Mu,而且401行看起来还比较复杂,让我们先根据我们的实例将其展开[_Bound_args对应int, std::_Placeholder<1>, std::_Placeholder<2>,
_Indexes=0,1,2] :

_Mu<int>()(std::get<0>(_M_bound_args), __args),
_Mu<std::_Placeholder<1>>()(std::get<1>(_M_bound_args), __args),
_Mu<std::_Placeholder<2>>()(std::get<2>(_M_bound_args), __args),

让我们再回忆一下:

  1. __args: 是一个tuple,由新函数传进来的参数组成的,此时为(3.14,“hello”)

  2. _M_bound_args:也是一个tuple,等于(42,_1,_2)
    上面的代码就变成了:

_Mu<int>()(42, tuple(3.14,"hello")),
_Mu<std::_Placeholder<1>>()(_1, tuple(3.14,"hello")),
_Mu<std::_Placeholder<2>>()(_2, tuple(3.14,"hello")),

可以盲猜一下:_Mu的作用就是把_n转成真正的参数,上面的三行最终要呈现出

423.14,
“hello”

这样的样子, 以便传给_M_f即原始函数。
让我们看看_Mu的代码是不是这样?

264   /**265    *  Maps an argument to bind() into an actual argument to the bound266    *  function object [func.bind.bind]/10. Only the first parameter should267    *  be specified: the rest are used to determine among the various268    *  implementations. Note that, although this class is a function269    *  object, it isn't entirely normal because it takes only two270    *  parameters regardless of the number of parameters passed to the271    *  bind expression. The first parameter is the bound argument and272    *  the second parameter is a tuple containing references to the273    *  rest of the arguments.274    */275   template<typename _Arg,276        bool _IsBindExp = is_bind_expression<_Arg>::value,277        bool _IsPlaceholder = (is_placeholder<_Arg>::value > 0)>278     class _Mu;

注释说的很明白,功能就是我们猜的那样。

4.3.2 幽灵_Mu的N个变体

_Mu还有n个偏特化,不过我们这只介绍两个,也是我们这里用到得两个:
第一个处理42,没有占位符的情况

 /*********************************对应_IsBindExp _IsPlaceholder 都是false
_Mu<int>()(42, tuple(3.14,"hello")), 返回42
**********************************/
352   /**353    *  If the argument is just a value, returns a reference to that354    *  value. The cv-qualifiers on the reference are determined by the caller.355    *  C++11 [func.bind.bind] p10 bullet 4.356    */357   template<typename _Arg>358     class _Mu<_Arg, false, false>359     {360     public:361       template<typename _CVArg, typename _Tuple>362     _CVArg&&363     operator()(_CVArg&& __arg, _Tuple&) const volatile364     { return std::forward<_CVArg>(__arg); }365     };

直接返回42,后面的tuple(3.14,“hello”)没用。
GDB调试:
在这里插入图片描述

第二种处理有占位符的情况

 /*********************************对应_IsBindExp=false _IsPlaceholder=true取tuple(3.14,"hello")中第n-1个(以下标0开始)_Mu<std::_Placeholder<1>>()(_1, tuple(3.14,"hello")),
_Mu<std::_Placeholder<2>>()(_2, tuple(3.14,"hello")),
**********************************/
339   template<typename _Arg>340     class _Mu<_Arg, false, true>341     {342     public:343       template<typename _Tuple>344     _Safe_tuple_element_t<(is_placeholder<_Arg>::value - 1), _Tuple>&&345     operator()(const volatile _Arg&, _Tuple& __tuple) const volatile346     {347       return348         ::std::get<(is_placeholder<_Arg>::value - 1)>(std::move(__tuple));349     }350     };

看下_1 _2时GDB的输出:
在这里插入图片描述

在这里插入图片描述
终于我们组齐了所有的碎片:

423.14,
“hello”

4.4 组齐碎片召唤神龙

到了召唤神龙的时候:

 398     __call(tuple<_Args...>&& __args, _Index_tuple<_Indexes...>)399     {400       return std::__invoke(_M_f,401           _Mu<_Bound_args>()(std::get<_Indexes>(_M_bound_args), __args)...402           );403     }

相当于:

return std::__invoke(_M_f, 42, 3.14, "hello");

全剧终!

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

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

相关文章

npm安装卡住问题(最新版)

npm安装卡住问题(最新版) 背景&#xff1a; ​ 最近这两天用npm安装一些包的时候&#xff0c;发现一直卡住&#xff1a; 报错&#xff1a; idealTree:npm: sill idealTree buildDeps之前能用的现在不能用了&#xff0c;我一想&#xff0c;是不是源头的问题&#xff0c;还真是…

C语言每日一题(48)回文链表

力扣 234 回文链表 题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1…

Redis性能运行参数的监测工具 - WGCLOUD

WGCLOUD是一款开源免费的运维监控平台&#xff0c;可以监测Redis的运行情况&#xff0c;比如redis的Key数量&#xff0c;过期Key数量&#xff0c;Redis的端口号&#xff0c;Redis的版本&#xff0c;同步状态&#xff0c;集群模式&#xff0c;使用内存等等数据 中间件Redis监测…

FinBert模型:金融领域的预训练模型

文章目录 模型及预训练方式模型结构训练语料预训练方式 下游任务实验结果实验一&#xff1a;金融短讯类型分类实验任务数据集实验结果 实验二&#xff1a;金融短讯行业分类实验任务数据集实验结果 实验三&#xff1a;金融情绪分类实验任务数据集实验结果 实验四&#xff1a;金融…

瑞_数据结构与算法_二叉搜索树

文章目录 1 什么是二叉搜索树1.1 二叉搜索树的特征1.2 前驱后继 2 二叉搜索树的Java实现2.1 定义二叉搜索树节点类BSTNode泛型key改进 2.2 实现查找方法get(int key)递归实现非递归实现 ★非递归实现 泛型key版本 2.3 实现查找最小方法min()递归实现非递归实现 ★ 2.4 实现查找…

Django从入门到精通(三)

目录 七、ORM操作 7.1、表结构 常见字段 参数 示例 7.2、表关系 一对多 多对多 第一种方式 第二种方式 7.3、连接MYSQL 7.4、数据库连接池 7.5、多数据库 读写分离 分库&#xff08;多个app ->多数据库&#xff09; 分库&#xff08;单app&#xff09; 注意…

狗东云搭建幻兽帕鲁(奶妈级别)

使用狗东云搭建幻兽帕鲁 同配置狗东云比腾讯云便宜&#xff0c;2核2G服务器仅50元1年&#xff0c;4核8G服务器458元1年&#xff0c;点击链接直达. 进入页面会跳转到注册&#xff0c;先注册账户&#xff0c;注册好后页面跳转&#xff0c;没有跳转点这里&#xff0c;选择页面左侧…

[AIGC 大数据基础] 浅谈hdfs

HDFS介绍 什么是HDFS&#xff1f; HDFS&#xff08;Hadoop Distributed File System&#xff09;是Apache Hadoop生态系统的一部分&#xff0c;是一个分布式文件系统。它被设计用于存储和处理大规模数据集&#xff0c;并且能够容错、高可靠和高性能地处理文件。 HDFS是为了支…

2024转行程序员的请注意:均月薪在40-70k

前言 2023年&#xff0c;对大多数行业来说都是不太好过的一年。 对程序员来说也是如此&#xff0c;很多粉丝朋友都在说android工作特别难找&#xff0c;一个岗位都是几千份简历........大家心里都是特别的焦虑&#xff0c;本以为2024年就业情况会有好转&#xff0c;但实际上并…

PHP - Yii2 异步队列

1. 前言使用场景 在 PHP Yii2 中&#xff0c;队列是一种特殊的数据结构&#xff0c;用于处理和管理后台任务。队列允许我们将耗时的任务&#xff08;如发送电子邮件、push通知等&#xff09;放入队列中&#xff0c;然后在后台异步执行。这样可以避免在处理大量请求时阻塞主应用…

[GXYCTF2019]BabySQli1

单引号闭合&#xff0c;列数为三列&#xff0c;但是没有期待的1 2 3回显&#xff0c;而是显示wrong pass。 尝试报错注入时发现过滤了圆括号&#xff0c;网上搜索似乎也没找到能绕过使用圆括号的方法&#xff0c;那么按以往爆库爆表爆字段的方法似乎无法使用了 在响应报文找到一…

ORM-07-querydsl 入门介绍

拓展阅读 The jdbc pool for java.(java 手写 jdbc 数据库连接池实现) The simple mybatis.&#xff08;手写简易版 mybatis&#xff09; 1. 介绍 1.1 背景 Querydsl的诞生源于以类型安全的方式维护HQL查询的需求。逐步构建HQL查询需要进行字符串连接&#xff0c;导致代码难以…

32个Java面试必考点-06常用工具集

本课时主要介绍常用的工具&#xff0c;将会讲解三个知识点&#xff1a; & JVM 相关工具的作用和适用场景&#xff1b; & Git 常用命令和工作流&#xff1b; & Linux 系统中常用分析工具。 常用工具汇总 常用工具汇总如下图所示。 说明&#xff1a;这里列出的都…

k8s的图形化工具--rancher

什么是rancher&#xff1f; rancher是一个开源的企业级多集群的k8s管理平台 rancher和k8s的区别 都是为了容器的调度和编排系统&#xff0c;但是rancher不仅能够调度&#xff0c;还能管理k8s集群&#xff0c;自带监控&#xff08;普罗米修斯&#xff09; 实验部署 实验架构…

电容主要特点和作用,不同类型的电容区别

电容 两个相互靠近的金属板中间夹一层绝缘介质组成的器件&#xff0c;当两端存在电势差时&#xff0c;由于介质阻碍了电荷移动而累积在金属板上&#xff0c;衡量金属板上储存电荷的能力称之为电容&#xff0c;相应的器件称为电容器。 电容&#xff08;Capacitance&#xff09…

移动端 h5-table react版本支持虚拟列表

介绍 适用于 react ts 的 h5 移动端项目 table 组件 github 链接 &#xff1a;https://github.com/duKD/react-h5-table 有帮助的话 给个小星星 有两种表格组件 常规的&#xff1a; 支持 左侧固定 滑动 每行点击回调 支持 指定列排序 支持滚动加载更多 效果和之前写的vue…

【开源】基于JAVA的实验室耗材管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 耗材档案模块2.2 耗材入库模块2.3 耗材出库模块2.4 耗材申请模块2.5 耗材审核模块 三、系统展示四、核心代码4.1 查询耗材品类4.2 查询资产出库清单4.3 资产出库4.4 查询入库单4.5 资产入库 五、免责说明 一、摘要 1.1…

四、Kotlin 表达式

1. 常量 & 变量 1.1 可读写变量&#xff08;var&#xff09; var x initValue // x 称为可读写变量注意&#xff1a;当 var 声明的变量做成员属性时&#xff0c;默认提供 setter/getter 方法。 1.2 只读变量&#xff08;val&#xff09; val x initValue // x 称为只…

FPGA:我的零基础学习路线(2022秋招已上岸)持续更新中~

可内推简历&#xff0c;丝我即可 前言 初次接触FPGA是在2022年3月左右&#xff0c;正处在研二下学期&#xff0c;面临着暑假找工作&#xff0c;周围的同学大多选择了互联网&#xff0c;出于对互联网的裁员形势下&#xff0c;我选择了FPGA&#xff0c;对于硬件基础知识我几乎是…

Vue+OpenLayers7入门到实战:鹰眼控件简单介绍,并使用OpenLayers7在地图上添加鹰眼控件

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7 前言 本章介绍OpenLayers7添加鹰眼控件到地图上的功能。 在OpenLayers中,想要实现鹰眼控件,必须要新建一个数据源,且不能跟其他图层混用,相当于鹰眼是一个单独图层。 补充知识,鹰眼控件是什么? 鹰眼控件是一种在地…