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

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与其他语言的不同点之一就是可以直接操作内存&#xff0c;这是一把双刃剑&#xff0c;直接操作内存可以提高开发的灵活度&#xff0c;开发人员在合适的时机申请内存&#xff0c;在合适的时机释放内存&#xff0c;减少…...

Buuctf reverse [FlareOn4]IgniteMe 题解

一. 查壳 无壳32位程序 二. ida打开 GetStdHandle函数根据微软官方文档可以得知是获取标准输入/输出/错误的句柄 参数里的 0xFFFFFFF6转换一下是4294967286, 对应(DWORD) -10 所以这里的WriteFile函数实际上是实现了printf的功能 sub_4010F0()函数 其功能是通过ReadFile函数读取…...

ChatGPT懂SAP吗?跟ChatGPT聊完后,我暂时不担心会失业

我弄了个ChatGPT账号。随便问了一下他SAP的问题。贴出来跟大家分享一下。 问题1&#xff1a;我是 SAP 开发工程师&#xff0c;你有什么可以帮到我。 作为一个语言模型&#xff0c;我可以为您提供关于SAP开发的一般性建议&#xff0c;以及关于如何使用SAP软件的一些技巧。以下是…...

Communications link failure 解决方法 !!!

Communications link failure 解决方法 最近在使用mybatis逆向生成的时候遇到了link failure的问题, 这里记录解决方法 文章目录Communications link failure 解决方法generatorConfig 里的数据库链接或数据库或密码或账户填写错误mysql响应时间设置太短, 还没等链接上就关闭连…...

pytorch入门2--数据预处理、线性代数的矩阵实现、求导

数据预处理是指将原始数据读取进来使得能用机器学习的方法进行处理。 首先介绍csv文件&#xff1a; CSV 代表逗号分隔值&#xff08;comma-separated values&#xff09;&#xff0c;CSV 文件就是使用逗号分隔数据的文本文件。 一个 CSV 文件包含一行或多行数据&#xff0c;每一…...

15.消息队列RabbitMQ

一、基本概念 RabbitMQ 是一个开源的AMQP实现&#xff0c;服务器端用Erlang语言编写&#xff0c;支持多种客户端&#xff0c;如&#xff1a;Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等&#xff0c;支持AJAX。用于在分布式系统中存储转发消息&#xf…...

并发编程之死锁问题介绍

一、本文概览 死锁问题在并发编程中是一个非常致命的问题&#xff0c;问题一旦产生&#xff0c;只能通过重启机器、修改代码来修复问题&#xff0c;下面我们通过一小段文章内容介绍下死锁以及如何死锁的预防 二、什么是死锁&#xff1f; 在介绍死锁之前&#xff0c;先来明确下什…...

【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.什么是循环依赖&#xff1a; 这里给大家举个简单的例子&#xff0c;相信看了上一篇文章大家都知道了解了spring的生命周期创建流程。那么在Spring在生命周期的哪一步会出现循环依赖呢&#xff1f; 第一阶段&#xff1a;实例化阶段 Instantiation 第二阶段&#xff1a;属性赋…...

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北航敏捷软件工程这个作业的要求在哪里个人作业-阅读和提问我在这个课程的目标是体验敏捷开发过程&#xff0c;掌握一些开发技能&#xff0c;为进一步发展作铺垫这个作业在哪个具体方面帮助我实现目标对本课…...

urho3d的自定义文件格式

Urho3D尽可能使用现有文件格式&#xff0c;仅在绝对必要时才定义自定义文件格式。当前使用的自定义文件格式有&#xff1a; 二进制模型格式&#xff08;.mdl&#xff09; Model geometry and vertex morph data byte[4] Identifier "UMDL" or "UMD2" …...

spark第一章:环境安装

系列文章目录 spark第一章&#xff1a;环境安装 文章目录系列文章目录前言一、文件准备1.文件上传2.文件解压3.修改配置4.启动环境二、历史服务器1.修改配置2.启动历史服务器总结前言 spark在大数据环境的重要程度就不必细说了&#xff0c;直接开始吧。 一、文件准备 1.文件…...

MySQL---存储过程与存储函数的相关概念

MySQL—存储过程与存储函数的相关概念 存储函数和存储过程的主要区别&#xff1a; 存储函数一定会有返回值的存储过程不一定有返回值 存储过程和函数能后将复杂的SQL逻辑封装在一起&#xff0c;应用程序无需关注存储过程和函数内部复杂的SQL逻辑&#xff0c;而只需要简单地调…...

PMP值得考吗?

第一&#xff0c;PMP的价值体现 1、PMP是管理岗位必考证书。 多数企业会选择优先录用持PMP证书的管理人才&#xff0c;PMP成为管理岗位的必考证书。PMP在很多外企和国内中大型企业非常受重视&#xff0c;中石油、中海油、华为等等都会给内部员工做培训。 这些机构对项目管理…...

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型&#xff0c;在实例化时不能再定义为reg型&#xff0c;而应该是…...

Netty权威指南总结(一)

一、为什么选择Netty&#xff1a;API使用简单&#xff0c;开发门槛低&#xff0c;屏蔽了NIO通信的底层细节。功能强大&#xff0c;预制了很多种编解码功能&#xff0c;支持主流协议。定制能力强&#xff0c;可以通过ChannelHandler对通信框架进行灵活地拓展。性能高、成熟、稳定…...

Elasticsearch:如何轻松安全地对实时 Elasticsearch 索引重新索引你的数据

在很多的时候&#xff0c;由于一些需求&#xff0c;我们不得不修改索引的映射&#xff0c;也即 mapping&#xff0c;这个时候我们需要重新索引&#xff08;reindex&#xff09;来把之前的数据索引到新的索引中。槽糕的是&#xff0c;我们的这个索引还在不断地收集实时数据&…...

【算法笔记】前缀和与差分

第一课前缀和与差分 算法是解决问题的方法与步骤。 在看一个算法是否优秀时&#xff0c;我们一般都要考虑一个算法的时间复杂度和空间复杂度。 现在随着空间越来越大&#xff0c;时间复杂度成为了一个算法的重要指标&#xff0c;那么如何估计一个算法的时间复杂度呢&#xf…...

python实战应用讲解-【实战应用篇】函数式编程-八皇后问题(附示例代码)

目录 知识储备-迭代器相关模块 itertools 模块 创建新的迭代器 根据最短输入序列长度停止的迭代器...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

Python常用模块:time、os、shutil与flask初探

一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...

SQL注入篇-sqlmap的配置和使用

在之前的皮卡丘靶场第五期SQL注入的内容中我们谈到了sqlmap&#xff0c;但是由于很多朋友看不了解命令行格式&#xff0c;所以是纯手动获取数据库信息的 接下来我们就用sqlmap来进行皮卡丘靶场的sql注入学习&#xff0c;链接&#xff1a;https://wwhc.lanzoue.com/ifJY32ybh6vc…...

起重机起升机构的安全装置有哪些?

起重机起升机构的安全装置是保障吊装作业安全的关键部件&#xff0c;主要用于防止超载、失控、断绳等危险情况。以下是常见的安全装置及其功能和原理&#xff1a; 一、超载保护装置&#xff08;核心安全装置&#xff09; 1. 起重量限制器 功能&#xff1a;实时监测起升载荷&a…...

云原生时代的系统设计:架构转型的战略支点

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 一、云原生的崛起&#xff1a;技术趋势与现实需求的交汇 随着企业业务的互联网化、全球化、智能化持续加深&#xff0c;传统的 I…...