C++使用shared_ptr与weak_ptr轻松管理内存
智能指针之shared_ptr与weak_ptr
- 前言
- 智能指针
- 实例分析
前言
C++与其他语言的不同点之一就是可以直接操作内存,这是一把双刃剑,直接操作内存可以提高开发的灵活度,开发人员在合适的时机申请内存,在合适的时机释放内存,减少冗余内存的占用,听起来非常不错。然而, 实际情况是申请了内存,忘记了释放,导致内存泄漏;又或者是,申请了内存,在某些情况下被释放了,然而另一部分代码却在继续使用这块内存,导致访问了非法内存,程序崩溃。当然,大部分的内存泄漏与访问非法内存导致的程序崩溃在debug版本中都是可以被发现的,但是还是会存在一些比较隐秘的角落,在测试期间发现不到,导致用户在试用期间发生崩溃,这是最糟的情况。
智能指针
C++11的新特性之一就是增加了三组智能指针,shared_ptr,weak_ptr,unique_ptr,通过合理的使用者三组智能指针,可以极大的避免开发过程中有关内存的困扰。今天我们只介绍前两个。
shard_ptr是可以对引用进行计数的,当有其他地方引用该指针时,引用计数加1,当有地方释放该指针时,引用计数减一,只有当引用计数未0时,这个指针才会被真正的析构。
weak_ptr不会增加引用计数,它一般是与shard_ptr搭配使用,使用shared_ptr来构造weak_ptr,weak_ptr调用lock()方法,如果该shared_ptr已经被析构了,则返回null,如果还没被析构,则返回一个shared_ptr,并且该shared_ptr的引用计数加1。
引用C++的经典书籍 **《Effective C++》**第三版中的一段话来感受下shared_ptr的优点
实际上,返回shard_ptr让接口设计者得以阻止一大群客户犯下资源泄漏的错误,因为就如条款14所言,shard_ptr允许当前智能指针被建立起来时指定一个资源释放函数(所谓删除其,“deleter”)绑定与智能指针身上。
shared_ptr有一个特别好的性质是:它会自动使用它的"每个指针专属的删除器",因而消除另一个潜在的客户错误:所谓的 “cross-DLL problem”。这个问题发生于“对象在动态链接程序库(DLL)中被new创建,却在另一个DLL内被delete销毁“。在许多平台上,这一类“跨DLL之new/delete成对运用”会导致运行期错误。 shared_ptr没有这个问题,因为它缺省的删除器是来自"shared_ptr诞生的那个DLL"的delete。
在C++开发中,遇到的问题越多,越会觉得**《Effective C++》第三版**真是一本好书,全书55个条款,分为
- 让自己习惯C++
- 构造/析构/赋值运算
- 资源管理
- 设计与声明
- 实现
- 继承与面向对象设计
- 模板与泛型编程
- 定值new与delete
- 杂项讨论
这九大部分,每一项条款都直击问题要害,给出防范措施,搭配**《C++ Primer》**阅读,C++程序员必备!
网上买**《Effective C++》加一本《C++ Primer》大概要100块钱,如果有需要的同学可以关注公众号程序员DeRozan**,回复1207直接领取。
下面来通过一个实例来感受下智能指针。
实例分析
下面我们通过分析一个例子,来大概感受下智能指针的好处。
一个服务,会接收一个cmd以及其携带的参数,并将其存入一个任务队列,交给一个任务线程去处理。
worker是一个对象,其有一个成员函数可以处理这个cmd。
函数asyncTaskHandler将这个cmd的handler函数以及参数封装到一个结构体中,然后放入队列m_taskQue中,会有任务线程来取任务并执行。
生产者
struct AICTask_t {int cmd;int task_id;taskStatus status;BaseWorker* worker;arg_t* arg;
};int AICTaskManager::asyncTaskHandler(int cmd, std::string &reqMsg, int reqMsgLen, std::string &respMsg, int &respMsgLen)
{TdInfo("AICTaskManager::asyncTaskHandler");TdInfo("create task");AICTask_t task;auto iter = m_workerMap.find(cmd); //m_workerMap : std::map<int, BaseWorker*> if(iter != m_workerMap.end()) {task->worker = iter->second;task->arg.cmd = cmd;task->arg.reqMsg = reqMsg;task->arg.reqMsgLen = reqMsgLen;task->arg.respMsg = respMsg;task->arg.respMsgLen = respMsgLen;task->status = prepare_to_start;}else{TdError("not found this cmd");return -1;}pthread_mutex_lock(&m_workerLock);while (m_taskQue.size() == m_maxQueSize && !m_stop){pthread_cond_wait(&m_cond_not_full, &m_workerLock);}m_taskQue.push(&task); //std::queue<AICTask_t*> m_taskQue;m_taskMap[m_task_id] = task;m_task_id++;pthread_mutex_unlock(&m_workerLock);pthread_cond_broadcast(&m_cond_not_empty);TdInfo("insert a task to queue");return 0;
}
aicThreadHandle是任务线程的线程入口函数,负责将handler以及参数拿到,并执行。
消费者
void *AICTaskManager::aicThreadHandle(void *arg)
{if(!arg) {TdError("bad parameter");return nullptr;}AICTaskManager *taskManager = (AICTaskManager *)arg;pthread_mutex_lock(&taskManager->m_workerLock);//loop for get task,every get lock, can get task while(true){// if m_taskQue not empty, get task, if empty, wait to get task// when task queue not empty, only one creater will wake up condition.while (taskManager->m_taskQue.empty() && !taskManager->m_stop){TdDebug("waiting to wake up");pthread_cond_wait(&taskManager->m_cond_not_empty, &taskManager->m_workerLock);// m_cond was waked up and get lock}//step while, now m_taskQue not emptyif (taskManager->m_stop){pthread_mutex_unlock(&taskManager->m_workerLock);TdInfo("thread % exit", pthread_self());pthread_exit(NULL);}AICTask_t* task = taskManager->m_taskQue.front();taskManager->m_taskQue.pop();pthread_mutex_unlock(&taskManager->m_workerLock);pthread_cond_broadcast(&taskManager->m_cond_not_full);TdInfo("exec function");task->status = running;(task->worker->handleCommand)(task->arg->cmd, task->arg->reqMsg, task->arg->reqMsgLen, task->arg->respMsg, task->arg->respMsgLen);task->status = over;}return nullptr;
}
上述代码的逻辑很简单,简单的 生产者消费者模式,动态申请的内存全部使用的裸指针来保存,而且这个代码中有一个很明显的错误。
生产者代码的第13行,AICTask_t task是在栈上申请的变量,存入std::queue<AICTask_t*> m_taskQue, m_taskQue内保存的是指针类型,所以在第34行通过取地址符传入了一个指针指向栈上的task,结果程序在消费者的第41行崩溃了。
为什么会崩溃呢,原因就是生产者把指向task的指针放入任务队列后,函数就执行结束了,task是栈上的变量,随着程序结束就被释放了,然而,m_taskQue还保存着指向task的指针,指向了一块未初始化的内存。在任务处理函数中,这个指针被访问了,程序就崩溃了。
为什么不在堆上申请task呢,因为我觉得在堆上申请还需要手动释放,容易出问题,所以想偷个懒,直接在栈上申请了,没有考虑到上面说的问题。
消费者的代码中,对于裸指针都是直接访问的,也没有检查是不是已经被释放了。
总之,手动管理内存实在太容易出问题了,所以,使用智能指针来管理内存,是很不错的选择。
将上面的代码进行改造,将需要用到指针的地方改为shared_ptr,并使用weak_ptr来检查智能指针已经被析构。
改造过后:
生产者
int AICTaskManager::asyncTaskHandler(int cmd, std::string &reqMsg, int reqMsgLen, std::string &respMsg, int &respMsgLen)
{TdInfo("AICTaskManager::asyncTaskHandler");TdInfo("create task");std::shared_ptr<AICTask_t> task = std::make_shared<AICTask_t>();auto iter = m_workerMap.find(cmd);if(iter != m_workerMap.end()) {task->worker = iter->second;task->arg.cmd = cmd;task->arg.reqMsg = reqMsg;task->arg.reqMsgLen = reqMsgLen;task->arg.respMsg = respMsg;task->arg.respMsgLen = respMsgLen;task->status = prepare_to_start;}else{TdError("not found this cmd");return -1;}pthread_mutex_lock(&m_workerLock);while (m_taskQue.size() == m_maxQueSize && !m_stop){pthread_cond_wait(&m_cond_not_full, &m_workerLock);}m_taskQue.push(task);m_taskMap[m_task_id] = task;m_task_id++;pthread_mutex_unlock(&m_workerLock);pthread_cond_broadcast(&m_cond_not_empty);TdInfo("insert a task to queue");return 0;
}
消费者
void *AICTaskManager::aicThreadHandle(void *arg)
{if(!arg) {TdError("bad parameter");return nullptr;}AICTaskManager *taskManager = (AICTaskManager *)arg;pthread_mutex_lock(&taskManager->m_workerLock);//loop for get task,every get lock, can get task while(true){// if m_taskQue not empty, get task, if empty, wait to get task// when task queue not empty, only one creater will wake up condition.while (taskManager->m_taskQue.empty() && !taskManager->m_stop){TdDebug("waiting to wake up");pthread_cond_wait(&taskManager->m_cond_not_empty, &taskManager->m_workerLock);// m_cond was waked up and get lock}//step while, now m_taskQue not emptyif (taskManager->m_stop){pthread_mutex_unlock(&taskManager->m_workerLock);TdInfo("thread % exit", pthread_self());pthread_exit(NULL);}// std::weak_ptr<AICTask_t> task = taskManager->m_taskQue.front();std::shared_ptr<AICTask_t> task = std::weak_ptr<AICTask_t>(taskManager->m_taskQue.front()).lock();if(!task){TdError("expired task");return nullptr;}taskManager->m_taskQue.pop();pthread_mutex_unlock(&taskManager->m_workerLock);pthread_cond_broadcast(&taskManager->m_cond_not_full);TdInfo("exec function");task->status = running;std::shared_ptr<BaseWorker> worker = std::weak_ptr<BaseWorker>(task->worker).lock();if (!worker){TdError("worker expired");return nullptr;}(worker->handleCommand)(task->arg.cmd, task->arg.reqMsg, task->arg.reqMsgLen, task->arg.respMsg, task->arg.respMsgLen);task->status = over;}return nullptr;
}
这样,我们在使用shared_ptr前,先通过weak_ptr判断是够可用,然后使用完成后,该指针会自动将引用计数减一,等到引用计数未0,也就是没有任何地方再引用它,就可以释放它指向的那块内存了。
不过需要注意的是,shard_ptr一定要用make_shared来构造,虽然有时携程shared_ptr<T>(new T())也能通过编译,但是这是会导致内存泄漏的。
智能指针的用法非常多,本文讲的是最常见的一种情况,其他更复杂的用法,则需要继续学习与发现。
相关文章:
C++使用shared_ptr与weak_ptr轻松管理内存
智能指针之shared_ptr与weak_ptr前言智能指针实例分析前言 C与其他语言的不同点之一就是可以直接操作内存,这是一把双刃剑,直接操作内存可以提高开发的灵活度,开发人员在合适的时机申请内存,在合适的时机释放内存,减少…...
Buuctf reverse [FlareOn4]IgniteMe 题解
一. 查壳 无壳32位程序 二. ida打开 GetStdHandle函数根据微软官方文档可以得知是获取标准输入/输出/错误的句柄 参数里的 0xFFFFFFF6转换一下是4294967286, 对应(DWORD) -10 所以这里的WriteFile函数实际上是实现了printf的功能 sub_4010F0()函数 其功能是通过ReadFile函数读取…...
ChatGPT懂SAP吗?跟ChatGPT聊完后,我暂时不担心会失业
我弄了个ChatGPT账号。随便问了一下他SAP的问题。贴出来跟大家分享一下。 问题1:我是 SAP 开发工程师,你有什么可以帮到我。 作为一个语言模型,我可以为您提供关于SAP开发的一般性建议,以及关于如何使用SAP软件的一些技巧。以下是…...
Communications link failure 解决方法 !!!
Communications link failure 解决方法 最近在使用mybatis逆向生成的时候遇到了link failure的问题, 这里记录解决方法 文章目录Communications link failure 解决方法generatorConfig 里的数据库链接或数据库或密码或账户填写错误mysql响应时间设置太短, 还没等链接上就关闭连…...
pytorch入门2--数据预处理、线性代数的矩阵实现、求导
数据预处理是指将原始数据读取进来使得能用机器学习的方法进行处理。 首先介绍csv文件: CSV 代表逗号分隔值(comma-separated values),CSV 文件就是使用逗号分隔数据的文本文件。 一个 CSV 文件包含一行或多行数据,每一…...
15.消息队列RabbitMQ
一、基本概念 RabbitMQ 是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息…...
并发编程之死锁问题介绍
一、本文概览 死锁问题在并发编程中是一个非常致命的问题,问题一旦产生,只能通过重启机器、修改代码来修复问题,下面我们通过一小段文章内容介绍下死锁以及如何死锁的预防 二、什么是死锁? 在介绍死锁之前,先来明确下什…...
【python学习笔记】:SQL常用脚本(一)
1、行转列的用法PIVOT CREATE table test (id int,name nvarchar(20),quarter int,number int) insert into test values(1,N苹果,1,1000) insert into test values(1,N苹果,2,2000) insert into test values(1,N苹果,3,4000) insert into test values(1,N苹果,4,5000) insert…...
Spring是怎么解决循环依赖的
1.什么是循环依赖: 这里给大家举个简单的例子,相信看了上一篇文章大家都知道了解了spring的生命周期创建流程。那么在Spring在生命周期的哪一步会出现循环依赖呢? 第一阶段:实例化阶段 Instantiation 第二阶段:属性赋…...
HTML创意动画代码
目录1、动态气泡背景2、创意文字3、旋转立方体1、动态气泡背景 <!DOCTYPE html> <html> <head><title>Bubble Background</title><style>body {margin: 0;padding: 0;height: 100vh;background: #222;display: flex;flex-direction: colum…...
软工第一次个人作业——阅读和提问
软工第一次个人作业——阅读和提问 项目内容这个作业属于哪个课程2023北航敏捷软件工程这个作业的要求在哪里个人作业-阅读和提问我在这个课程的目标是体验敏捷开发过程,掌握一些开发技能,为进一步发展作铺垫这个作业在哪个具体方面帮助我实现目标对本课…...
urho3d的自定义文件格式
Urho3D尽可能使用现有文件格式,仅在绝对必要时才定义自定义文件格式。当前使用的自定义文件格式有: 二进制模型格式(.mdl) Model geometry and vertex morph data byte[4] Identifier "UMDL" or "UMD2" …...
spark第一章:环境安装
系列文章目录 spark第一章:环境安装 文章目录系列文章目录前言一、文件准备1.文件上传2.文件解压3.修改配置4.启动环境二、历史服务器1.修改配置2.启动历史服务器总结前言 spark在大数据环境的重要程度就不必细说了,直接开始吧。 一、文件准备 1.文件…...
MySQL---存储过程与存储函数的相关概念
MySQL—存储过程与存储函数的相关概念 存储函数和存储过程的主要区别: 存储函数一定会有返回值的存储过程不一定有返回值 存储过程和函数能后将复杂的SQL逻辑封装在一起,应用程序无需关注存储过程和函数内部复杂的SQL逻辑,而只需要简单地调…...
PMP值得考吗?
第一,PMP的价值体现 1、PMP是管理岗位必考证书。 多数企业会选择优先录用持PMP证书的管理人才,PMP成为管理岗位的必考证书。PMP在很多外企和国内中大型企业非常受重视,中石油、中海油、华为等等都会给内部员工做培训。 这些机构对项目管理…...
Quartus 报错汇总(持续更新...)
1、Error (10663): Verilog HDL Port Connection error at top_rom.v(70): output or inout port "stcp" must be connected to a structural net expression输出变量stcp在原设计文件中已经定义为reg型,在实例化时不能再定义为reg型,而应该是…...
Netty权威指南总结(一)
一、为什么选择Netty:API使用简单,开发门槛低,屏蔽了NIO通信的底层细节。功能强大,预制了很多种编解码功能,支持主流协议。定制能力强,可以通过ChannelHandler对通信框架进行灵活地拓展。性能高、成熟、稳定…...
Elasticsearch:如何轻松安全地对实时 Elasticsearch 索引重新索引你的数据
在很多的时候,由于一些需求,我们不得不修改索引的映射,也即 mapping,这个时候我们需要重新索引(reindex)来把之前的数据索引到新的索引中。槽糕的是,我们的这个索引还在不断地收集实时数据&…...
【算法笔记】前缀和与差分
第一课前缀和与差分 算法是解决问题的方法与步骤。 在看一个算法是否优秀时,我们一般都要考虑一个算法的时间复杂度和空间复杂度。 现在随着空间越来越大,时间复杂度成为了一个算法的重要指标,那么如何估计一个算法的时间复杂度呢…...
python实战应用讲解-【实战应用篇】函数式编程-八皇后问题(附示例代码)
目录 知识储备-迭代器相关模块 itertools 模块 创建新的迭代器 根据最短输入序列长度停止的迭代器...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
