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监测…

【前端基础--3】

文字样式 1.文字颜色 color 取值方式&#xff1a; 英文单词 red green blue十六进制的颜色值 #000000 也可以写为#000&#xff08;如aabbcc可以简写为abc&#xff09;rgb三原色取值 color&#xff1a;rgb(220,32,215) 取值范围都在0~255之间 2.文字大小 font-size …

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 实现查找…

mavros和PX4中的海拔高与椭球高转换

飞控高度传感器中一般有两种高度&#xff1a; 海拔高。也称AMSL&#xff08;Above Mean Sea Level&#xff09;height或者geoid height或者正高&#xff0c;顾名思义就是指高于当地平均海平面的高度。我猜气压计测得的高度应当就是与海平面相关的。椭球高。也称ellipsoid heig…

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;导致代码难以…

迭代器模式-C#实现

该实例基于WPF实现&#xff0c;直接上代码&#xff0c;下面为三层架构的代码。 目录 一 Model 二 View 三 ViewModel 一 Model using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 设计模式练…

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

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

journalctl日期范围操作

这里以Docker 日志为例 要查看特定时间段的 Docker 服务日志&#xff0c;你可以使用 journalctl 命令&#xff0c;并结合 -u 选项来指定服务单元名称&#xff08;docker.service&#xff09;以及 -n 和 -S 选项来限制日志的数量和时间范围。 以下是一个示例命令&#xff0c;用…

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…