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 模块 创建新的迭代器 根据最短输入序列长度停止的迭代器...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果