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 function
    auto boundFunction3 = std::bind(printValues, 42, 3.14, "Hello");
        std::cout<<"sizeof(boundFunction3):"<<sizeof(boundFunction3)<<std::endl;
    // Invoking the bound function
    boundFunction3();  // Output: Values: 42, 3.14, Hello

    auto boundFunction1 = std::bind(printValues, 42, std::placeholders::_1, std::placeholders::_2);
        std::cout<<"sizeof(boundFunction1):"<<sizeof(boundFunction1)<<std::endl;
    // Invoking the bound function
    boundFunction1(3.14,"hello");  // Output: Values: 42, 3.14, Hello
    return 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)>::__type
 390     _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 unqualified
 396       template<typename _Result, typename... _Args, std::size_t... _Indexes>
 397     _Result
 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     }

这里又出现了没见过的模板类:_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 bound
 266    *  function object [func.bind.bind]/10. Only the first parameter should
 267    *  be specified: the rest are used to determine among the various
 268    *  implementations. Note that, although this class is a function
 269    *  object, it isn't entirely normal because it takes only two
 270    *  parameters regardless of the number of parameters passed to the
 271    *  bind expression. The first parameter is the bound argument and
 272    *  the second parameter is a tuple containing references to the
 273    *  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 that
 354    *  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 volatile
 364     { 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 volatile
 346     {
 347       return
 348         ::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");

全剧终!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++干货基地】C++入门篇:输入输出流 | 缺省函数 | 函数重载(文末送书)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

JVM-初始JVM

什么是JVM JVM 全称是 Java Virtual Machine&#xff0c;中文译名 Java虚拟机。JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 Java源代码执行流程如下&#xff1a; JVM的功能 1 - 解释和运行 2 - 内存管理 3 - 即时编译 解释和运行 解释…

如何配置Tomcat服务环境并实现无公网ip访问本地站点

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

重塑网络安全格局:零信任安全架构的崛起与革新

零信任安全架构是一种现代安全模式&#xff0c;其设计原则是“绝不信任&#xff0c;始终验证”。它要求所有设备和用户&#xff0c;无论他们是在组织网络内部还是外部&#xff0c;都必须经过身份验证、授权和定期验证&#xff0c;才能被授予访问权限。简而言之&#xff0c;“零…

(2024,MLLM,扩散,中文数据集扩散预训练,多模态提示引导微调)UNIMO-G:通过多模态条件扩散进行统一图像生成

UNIMO-G: Unified Image Generation through Multimodal Conditional Diffusion 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 1. 摘要 2. 方法 3. 结果 1. 摘要 现有的文本到图像…

GraphQL的力量:简化复杂数据查询

1. GraphQL GraphQL 是一种由 Facebook 开发并于 2015 年公开发布的数据查询和操作语言&#xff0c;也是运行在服务端的运行时&#xff08;runtime&#xff09;用于处理 API 查询的一种规范。不同于传统的 REST API&#xff0c;GraphQL 允许客户端明确指定它们需要哪些数据&am…

java以SSL方式连ES

先做准备工作&#xff0c;浏览器方式访问 ES7.X url https://127.0.0.1:8027 弹出用户名和密码 输入后在浏览器得到 { “name” : “DTCNPEMS04”, “cluster_name” : “cnp-es-cluster”, “cluster_uuid” : “wb0So_FqQBOKqtXnsqofTg”, “version” : { “number” : “7.…

力扣hot100 两数相加 链表 思维

Problem: 2. 两数相加 Code ⏰ 时间复杂度: O ( n ) O(n) O(n) &#x1f30e; 空间复杂度: O ( n ) O(n) O(n) /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.…

prometheus监控RabbitMQ策略

一般用官方的rabbitmq_exporter采取数据即可&#xff0c;然后在普米配置。但如果rabbitmq节点的队列数超过了5000&#xff0c;往往rabbitmq_exporter就会瘫痪&#xff0c;因为rabbitmq_exporter采集的信息太多&#xff0c;尤其是那些队列的细节&#xff0c;所以队列多了&#x…

android camera的使用以及输出的图像格式

一、Camera 1.1、结合SurfaceView实现预览 1.1.1、布局 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.com/apk/res-au…

SpringBoot项目多数据源配置与MyBatis拦截器生效问题解析

在日常项目开发中&#xff0c;由于某些原因&#xff0c;一个服务的数据源可能来自不同的库&#xff0c;比如&#xff1a; 对接提供的中间库&#xff0c;需要查询需要的数据同步数据&#xff0c;需要将一个库的数据同步到另一个库&#xff0c;做为同步工具的服务对接第三方系统…

离零售业智能体时代的真正开启还有多远?

AIGC&#xff08;生成式人工智能&#xff09;当道的2023年&#xff0c;将LLM&#xff08;大语言模型&#xff09;的各类生成式能力发挥到淋漓尽致、精彩纷呈的程度。各行各业一边在观望大语言模型不断扩宽的商业运用可能&#xff0c;一边在继续探寻能够不断拓宽企业往纵深发展的…

C/C++ - Auto Reference

目录 auto Reference auto 当使用auto​​关键字声明变量时&#xff0c;C编译器会根据变量的初始化表达式推断出变量的类型。 自动类型推断&#xff1a;auto​​关键字用于自动推断变量的类型&#xff0c;使得变量的类型可以根据初始化表达式进行推导。 初始化表达式&#x…

Redis的五种常用数据类型详解及相关面试问题

目录 Redis的五种常用数据类型详解 简述 Redis五种基本数据类型 String字符串 常用命令 应用场景 Hash散列表 常用命令 使用场景 List链表 常用命令 应用场景 Set( 集合) 常用命令 应用场景 SortedSet( 有序集合) zset 常用命令介绍 应用场景 面试题常问的数…

【驱动】TI AM437x(内核调试-07):devmem2直接读写内存、寄存器,devkmem读取内核变量

1、/dev/mem 和 /dev/kmem 1)/dev/mem: 物理内存的全镜像。可以用来访问物理内存 2)/dev/kmem: kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。kernel部分内存用户空间本不可访问。但是因为所有进程共享内核空间的页表。所以内核虚拟地址对应物理地址是确定的…

【码农新闻】 CSS 即将支持嵌套,SASS/LESS 等预处理器已无用武之地?常见的Web攻击手段,拿捏了!......

目录 【码农新闻】 CSS 即将支持嵌套&#xff0c;SASS/LESS 等预处理器已无用武之地&#xff1f;常见的Web攻击手段&#xff0c;拿捏了&#xff01;...... 流行框架与库的源码分析与最简实现CSS 即将支持嵌套&#xff0c;SASS/LESS 等预处理器已无用武之地&#xff1f;常见的W…

低代码开发平台与可组合业务:实现高效应用的完美结合

如今&#xff0c;有很多产品已经走在了模块化的道路上&#xff0c;例如一款吸尘器&#xff0c;它可以经由不同配件组合来实现不同的功能&#xff0c;来满足消费者的需求。这种类似于“一站式”的产品解决方案&#xff0c;正在成为一种可见的趋势。 今年年初&#xff0c;Gartne…

函数递归知识点与经典例题

目录 递归的概念 &#xff08;什么是递归&#xff09; 递归举例 举例1&#xff1a;求n的阶乘 举例2&#xff1a;顺序打印一个整数的每一位 递归与迭代 举例3&#xff1a;求第n个斐波那契数 递归的概念 &#xff08;什么是递归&#xff09; 递归是学习C语言函数绕不开的⼀…

第13章_泛型(集合中使用泛型,比较器中使用泛型,自定义泛型结构,泛型在继承上的体现,通配符的使用)

文章目录 第13章_泛型(Generic)本章专题与脉络1. 泛型概述1.1 生活中的例子1.2 泛型的引入 2. 使用泛型举例2.1 集合中使用泛型2.1.1 举例2.1.2 练习 2.2 比较器中使用泛型2.2.1 举例2.2.2 练习 2.3 相关使用说明 3. 自定义泛型结构3.1 泛型的基础说明3.2 自定义泛型类或泛型接…

一文学习Thrift RPC

Thrift RPC引言 Thrift RPC的特点 Thrift 是一个RPC的框架&#xff0c;和Hessian RPC有什么区别&#xff0c;最重要的区别是Thrift可以做异构系统开发。 什么是异构系统&#xff0c;服务的提供者和服务的调用者是用不同语言开发的。 为什么会当前系统会有异构系统的调用&…