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

手写一个std::function

前言

在《std::function从实践到原理》中我们分析了std::function的实现原理,但这只是纸上谈兵。要想理解为什么这么实现,最好的办法还是想想要是自己手写一个要怎么实现。本文不想直接呈现最终版本,因为那样读者看不到某段代码是为了什么才那么写。我会搭建好几版,争取把所想所思都体现出来。

第一版

我们先不考虑复制构造函数,也不考虑移动构造函数,只考虑最普通的构造函数, 一共两种类型可以赋值给Myfunction: lambda表达式、函数指针。这两种类型要么通过类模板参数要么通过构造函数的模板参数传进来,分别形如:

template<typename _Res, typename... _ArgTypes, typename _Functor>
class Myfunction 
//构造函数        
template<typename _Functor> 
Myfunction(_Functor __f){。。。
}

第一种要求用户必须这样使用:Myfunction<..., lambda0>, 但实际用户是给不出lambda表达式对应的类的(由编译器给出)。所以只能是第二种。然后还有个需求:用户传过来的lambda对象或者函数指针得copy一份,这样以后才能调用到它们(重载函数operator ()()中调用)所以这个Myfunction大概得长这样:

template<typename _Res, typename... _ArgTypes>
class Myfunction 
{
public:template<typename _Functor> Myfunction(_Functor __f){f = new _Functor(std::move(__f));}_Res operator()(_ArgTypes... __args) const{return (*f)(std::forward<_ArgTypes>(__args)...);}private:_Functor* f;
};

但这样有个大问题:_Functor作用域只在构造函数内,_Functor* f是编译不过的。所以f要承接lambda对象,也要承接函数指针,那只能是万能指针void* f或char* f了。

如果f成了void*类型,operator()()中还是得知道f原本的类型,不然没法编译通过。void* f不是callable的。_Functor只在构造函数中有,但operator()()却要使用,怎么办?

第二版

要解决这个问题,我们就得学一学std::function了。引入一个函数指针,它里面保留_Functor这个信息。

template<typename _Res, typename _Functor, typename... _ArgTypes>
class _MyFunction_handler
{
public:statc _Res _Function_handler_invoke(void* _Any_data,  _ArgTypes... __args){return (*(_Functor*)_Any_data)(std::forward<_ArgTypes>(__args)...);}
};template<typename _Res, typename... _ArgTypes>
class MyFunction;template<typename _Res, typename... _ArgTypes>
class MyFunction<_Res(_ArgTypes...)>
{
public:template<typename _Functor>MyFunction(_Functor __f){f = new _Functor(std::move(__f));_M_invoker = &_MyFunction_handler<_Res,_Functor,_ArgTypes...>::_Function_handler_invoke;}_Res operator()(_ArgTypes... __args) const{return (*_M_invoker)(f, std::forward<_ArgTypes>(__args)...);}private:void* f;typedef _Res (*_Invoker_type)(void* _Any_data, _ArgTypes...);_Invoker_type _M_invoker;
};

_M_invoker是一个桥梁,把构造和调用连接了起来(_Functor方面) 。写几个测试用例试一试:

int gAdd(int a, int b){return a+b;
}int main(){MyFunction<int(int,int)> f1 = [](int a,int b)->int {return a+b;};int res = f1(1,2);std::cout<<res<<std::endl;MyFunction<int(int,int)> f2 = gAdd;int res2 = f2(3,4);std::cout<<res2<<std::endl;
}

能走通了!

显然有个问题:new的东西没释放。依照_M_invoker再搞一个_M_destroy。

第三版

#include <iostream>template<typename _Res, typename _Functor, typename... _ArgTypes>
class _MyFunction_handler
{
public:statc _Res _Function_handler_invoke(void* _Any_data,  _ArgTypes... __args){return (*(_Functor*)_Any_data)(std::forward<_ArgTypes>(__args)...);}static void _Function_handler_destroy(void* _Any_data){//((_Functor*)_Any_data)->~_Functor();delete ((_Functor*)_Any_data);_Any_data = nullptr;}
};template<typename _Res, typename... _ArgTypes>
class MyFunction;template<typename _Res, typename... _ArgTypes>
class MyFunction<_Res(_ArgTypes...)>
{
public:template<typename _Functor>MyFunction(_Functor __f){f = new _Functor(std::move(__f));_M_invoker = &_MyFunction_handler<_Res,_Functor,_ArgTypes...>::_Function_handler_invoke;_M_destroy = &_MyFunction_handler<_Res,_Functor,_ArgTypes...>::_Function_handler_destroy;}_Res operator()(_ArgTypes... __args) const{return (*_M_invoker)(f, std::forward<_ArgTypes>(__args)...);}~MyFunction(){(*_M_destroy)(f);}
private:void* f;typedef _Res (*_Invoker_type)(void* _Any_data, _ArgTypes...);_Invoker_type _M_invoker;typedef void (*_Destroy_type)(void* _Any_data);_Destroy_type _M_destroy;};int gAdd(int a, int b){return a+b;
}int main(){MyFunction<int(int,int)> f1 = [](int a,int b)->int {return a+b;};int res = f1(1,2);std::cout<<res<<std::endl;MyFunction<int(int,int)> f2 = gAdd;int res2 = f2(3,4);std::cout<<res2<<std::endl;}

[mzhai@~]$ g++ myfunction.cpp -std=c++11 -g
[mzhai@~]$ ./a.out
3
7
[mzhai@c++11]$ valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt1 ./a.out
3
7

valgrind测试也没有内存泄漏。

完美?NO,还没有处理给Myfunction赋值nullptr的情况。 还没有写各种各样的构造函数。路已经走通了,这些都是修修补补,不再赘述。

相关文章:

手写一个std::function

前言 在《std::function从实践到原理》中我们分析了std::function的实现原理&#xff0c;但这只是纸上谈兵。要想理解为什么这么实现&#xff0c;最好的办法还是想想要是自己手写一个要怎么实现。本文不想直接呈现最终版本&#xff0c;因为那样读者看不到某段代码是为了什么才…...

04--MySQL函数的使用

1、SQL函数的使用 当我们学习编程语言的时候&#xff0c;经常会遇到函数。函数的好处是&#xff0c;它可以把我们经常使用的代码封装起来&#xff0c;需要的时候直接调用即可。这样既提高了编写代码的效率&#xff0c;又提高了可维护性。在SQL中函数主要要对数据进行处理&…...

imgaug库指南(28):从入门到精通的【图像增强】之旅(万字长文)

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…...

精确掌控并发:漏桶算法在分布式环境下并发流量控制的设计与实现

这是《百图解码支付系统设计与实现》专栏系列文章中的第&#xff08;16&#xff09;篇&#xff0c;也是流量控制系列的第&#xff08;3&#xff09;篇。点击上方关注&#xff0c;深入了解支付系统的方方面面。 本篇重点讲清楚漏桶原理&#xff0c;在支付系统的应用场景&#x…...

【Redis】Redis面试热点

Redis 集群有哪些方案&#xff1f; 主从复制&#xff1a;解决了高并发问题 哨兵模式&#xff1a;解决了高并发&#xff0c;高可用问题 分片集群&#xff1a;解决了海量数据存储&#xff0c;高并发写的问题 主从复制 图示&#xff1a; 主从复制&#xff1a;单节点 Redis 并发…...

构建中国人自己的私人GPT-有道GPT

创作不易&#xff0c;请大家多鼓励支持。 在现实生活中&#xff0c;很多人的资料是不愿意公布在互联网上的&#xff0c;但是我们又要使用人工智能的能力帮我们处理文件、做决策、执行命令那怎么办呢&#xff1f;于是我们构建自己或公司的私人GPT变得非常重要。 先看效果 一、…...

data = self._data_queue.get(timeout=timeout)

目录 解决方法 freeze_support 解决方法 opencv 升级 方法3 OMP_NUM_THREADS&#xff1a; 报错&#xff1a; data self._data_queue.get(timeouttimeout) 解决方法 freeze_support data self._data_queue.get(timeouttimeout)RuntimeError: DataLoader worker (pid(s)…...

推挽输出、开漏输出、上拉输入、下拉输入、浮空输入。

一、推挽输出 推挽输出的内部电路大概如上图中黄色部分&#xff0c;输出控制内有反相器&#xff0c;由一个P-MOS和一个N-MOS组合而成&#xff0c;同一时间只有一个管子能够进行导通。 当写入1时&#xff0c;经过反向器后为0&#xff0c;P-MOS导通&#xff0c;N-MOS截至&#xf…...

【Java JVM】栈帧

执行引擎是 Java 虚拟机核心的组成部分之一。 在《Java虚拟机规范》中制定了 Java 虚拟机字节码执行引擎的概念模型, 这个概念模型成为各大发行商的 Java 虚拟机执行引擎的统一外观 (Facade)。 不同的虚拟机的实现中, 通常会有 解释执行 (通过解释器执行)编译执行 (通过即时编…...

【Emgu CV教程】5.4、几何变换之图像翻转

今天讲解的两个函数&#xff0c;可以实现以下样式的翻转。 水平翻转&#xff1a;将图像沿Y轴(图像最左侧垂直边缘)翻转的操作。原始图像中位于左侧的内容将移动到目标图像的右侧&#xff0c;原始图像中位于右侧的内容将移动到目标图像的左侧。垂直翻转&#xff1a;将图像沿X轴…...

2024年AMC8历年真题练一练和答案详解(10),以及全真模拟题

六分成长继续为您分享AMC8历年真题&#xff0c;最后两天通过高质量的真题来体会快速思考、做对题目的策略。 题目从575道在线题库&#xff08;来自于往年真题&#xff09;中抽取5道题&#xff0c;每道题目均会标记出自年份和当年度的序号&#xff0c;并附上详细解析。【使用六…...

echarts业务中常用属性设置记录

1.legend计算占比 //在data中定义两个字段 total:0, znum:0 //计算上面两个值 this.data.forEach(val > this.total parseInt(val.value)); for (let i 0; i < nv.length; i) {if (i ! nv.length - 1) {this.znum this.znum Number(parseFloat((nv[i].value / this.t…...

Ubuntu 22.04 安装prometheus

服务器监控和报警软件有很多&#xff0c;为什么我们会选择Prometheus而不是其他软件呢&#xff1f; 因为它有以下优点&#xff1a; 自带简易web监控页面&#xff0c;用户可以很方便地查看监控数据和使用仪表盘。能实时收集数据并根据自定义警报规则推送告警&#xff1b;具有丰…...

Django的模板语言

文章目录 模板语法变量标签过滤器注释 组件引擎模板上下文加载器上下文处理器 模板引擎的支持配置用法引擎内置后端 模板 作为一个网络框架&#xff0c;Django 需要一种方便的方式来动态生成 HTML。最常见的方法是依靠模板。一个模板包含了所需 HTML 输出的静态部分&#xff0…...

为什么安卓逆向手机要root

安卓逆向工程是指对安卓应用程序进行研究和分析&#xff0c;以了解其内部工作原理、提取资源、修改应用行为、发现漏洞等。在某些情况下&#xff0c;为了进行逆向分析&#xff0c;需要对手机进行Root。 以下是一些安卓逆向中可能需要Root的原因&#xff1a; 获得完全访问权限…...

整合junit与热部署

整合junit <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.7.0</version></dependency> 测试类上添加SpringBootTest 如&#xff1a; 注意测试类的…...

C++面试宝典第21题:字符串解码

题目 给定一个经过编码的字符串,返回其解码后的字符串。具体的编码规则为:k[encoded_string],表示方括号内部的encoded_string正好重复k次。注意:k保证为正整数;encoded_string只包含大小写字母,不包含空格和数字;方括号确定是匹配的,且可以嵌套。 示例: 编码字符串为…...

WXUI 基于uni-app x开发的高性能混合UI库

uni-app x 是什么&#xff1f; uni-app x&#xff0c;是下一代 uni-app&#xff0c;是一个跨平台应用开发引擎。 uni-app x 没有使用js和webview&#xff0c;它基于 uts 语言。在App端&#xff0c;uts在iOS编译为swift、在Android编译为kotlin&#xff0c;完全达到了原生应用…...

P9840 [ICPC2021 Nanjing R] Oops, It‘s Yesterday Twice More题解

[ICPC2021 Nanjing R] Oops, It’s Yesterday Twice More 传送门 题面翻译 有一张 n n n\times n nn 的网格图&#xff0c;每个格子上都有一只袋鼠。对于一只在 ( i , j ) (i,j) (i,j) 的袋鼠&#xff0c;有下面四个按钮&#xff1a; 按钮 U&#xff1a;如果 i > 1 …...

OceanBase与MySQL兼容性对比

OB针对于高并发和大数据更有优势&#xff0c;公司的dba让我们把数据从mysql迁移到OceanBase了&#xff0c;这里记录一下OceanBase的MySQL模式。 OceanBase的MySQL模式兼容MySQL5.7的绝大部分功能和语法,兼容MySQL5.7版本的全量以及8.0版本的部分JSON函数。 暂不支持的功能: O…...

用正点原子Nano开发板,5分钟搞定RT-Thread Nano的MDK5工程配置(附串口调试技巧)

正点原子Nano开发板极速上手RT-Thread实战指南 1. 开箱即用的开发环境搭建 刚拿到正点原子Nano开发板时&#xff0c;最令人兴奋的莫过于快速验证硬件是否正常工作。这款基于STM32F103RBT6的开发板&#xff0c;以其72MHz主频和丰富的外设资源&#xff0c;成为嵌入式入门学习的…...

Joy-Con Toolkit:一站式解决Switch手柄所有问题的智能管理工具

Joy-Con Toolkit&#xff1a;一站式解决Switch手柄所有问题的智能管理工具 【免费下载链接】jc_toolkit Joy-Con Toolkit 项目地址: https://gitcode.com/gh_mirrors/jc/jc_toolkit Joy-Con Toolkit是一款专为Nintendo Switch手柄设计的开源管理工具&#xff0c;为游戏玩…...

【云计算学习之路】学习Centos7系统:服务搭建(NFS)

文章目录【云计算学习之路】学习Centos7系统:服务搭建(NFS)前言一、NFS 核心原理与架构1.1 NFS 服务简介1.2 NFS 核心依赖与守护进程、端口机制1.2.1 基础依赖组件1.2.2 NFS 核心守护进程1.2.3 核心通信端口规则1.3 NFS 完整工作流程&#xff08;附原理图解&#xff09;1.4 常用…...

量子计算核心原理、技术路线与应用场景全解析

1. 量子计算&#xff1a;一场颠覆性的计算范式革命量子计算&#xff0c;这个词在科技圈已经火了很久&#xff0c;但很多人对它的理解可能还停留在“比超级计算机快无数倍”的模糊印象里。作为一名长期关注前沿技术的从业者&#xff0c;我亲眼见证了它从实验室里高深莫测的理论&…...

手把手用Python实现μ律/A律压缩算法(附完整代码与波形对比)

手把手用Python实现μ律/A律压缩算法&#xff08;附完整代码与波形对比&#xff09; 在数字音频处理领域&#xff0c;动态范围压缩是一个永恒的话题。想象一下&#xff0c;当你录制一段包含轻柔耳语和强烈鼓声的音频时&#xff0c;直接使用线性PCM编码会导致要么小声部分被量化…...

昇腾CANN amct:模型压缩工具的量化和部署实践

amct&#xff08;Ascend Model Compression Toolkit&#xff09;是 CANN 内置的模型压缩工具&#xff0c;不是 AtomGit 上的独立开源仓库——它在 CANN AOE 调优引擎里作为一个子模块运行。amct 做三件事&#xff1a;量化&#xff08;INT8/FP16&#xff09;、剪枝&#xff08;结…...

昇腾CANN cann-samples:从示例代码到生产力工具的全路径

CANN 55 个仓库里&#xff0c;cann-samples 是最容易被低估的一个。它不定义新算子、不优化性能、不做架构设计——只提供可运行的代码示例。但正是因为「只提供示例」&#xff0c;cann-samples 是新手最快上手、老手最常查阅的仓库。每个示例都是独立可编译的项目&#xff1a;…...

nodejs后端服务如何接入taotoken实现异步调用多模型对话能力

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Node.js 后端服务如何接入 Taotoken 实现异步调用多模型对话能力 1. 准备工作&#xff1a;获取 API Key 与模型 ID 在开始编写代码…...

opencode使用安装

确保已经安装好node npm安装opencode C:\WINDOWS\system32>npm install -g opencode-aiadded 3 packages in 2mC:\WINDOWS\system32>npm安装mcp-chrome C:\WINDOWS\system32>npm...

感知与建图,为什么不能只跑一个 SLAM Demo?

一、核心问题机器人要稳定工作&#xff0c;需要把视觉、激光、IMU、模型结果和ROS2协同整合到一条完整链路里&#xff0c;而不是只依赖单一的SLAM Demo。二、为什么SLAM Demo不够用&#xff1f;Demo的局限性&#xff1a;SLAM Demo只能证明单点功能能跑&#xff0c;无法覆盖实际…...