【CPP】智能指针

引言

智能指针是RAII思想的体现,有时候程序抛异常导致指针指向的内存资源未释放,造成内存泄漏,这时就需要用到智能指针,它可以出作用域自动调用析构函数释放内存资源

内存泄漏

什么是内存泄漏

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

解决方案

1.事前预防:采用RAII即智能指针
2.事后补救:用内存泄漏检测工具。

智能指针

template<class T>class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~SmartPtr(){delete _ptr;}private:T* _ptr;
};int div()
{int a, b;std::cin >> a >> b;if (b == 0)throw std::exception("division zero error");return a / b;
}void Func()
{SmartPtr<int> sp1(new int);//把申请的资源交给sp管理SmartPtr<int> sp2(new int);std::cout << div() << std::endl;
}int main()
{try{Func();}catch (std::exception& e){std::cout << e.what() << std::endl;}return 0;
}

智能指针的拷贝构造

auto_ptr

采用管理权转移的方式避免浅拷贝同一空间被析构两次

auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}

问题:

采用管理权转移方式,看似没问题,只让对象析构一次,另一个指针指向空。但会存在拷贝对象悬空问题,即再对拷贝对象解引用是对空指针解引用。

在这里插入图片描述

unique_ptr

采用delete关键直接禁止拷贝
在这里插入图片描述

shared_ptr

采用引用计数的方式,支持拷贝

template<class T>class SharedPtr
{
public:SharedPtr(T* ptr):_ptr(ptr),_pcount(new int(1)){}SharedPtr(const SharedPtr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount){AddCount();}SharedPtr<T>& operator=(const SharedPtr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;AddCount();}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}void Release(){if (--(*_pcount) == 0){std::cout << "delete:" << _ptr << std::endl;delete _ptr;delete _pcount;}}void AddCount(){++(*_pcount);}~SharedPtr(){Release();}private:T* _ptr;int* _pcount;
};void test1()
{SharedPtr<int> sp1(new int(1));SharedPtr<int> sp2(sp1);SharedPtr<int> sp4(new int(10));//sp4 = sp1;sp1 = sp4;
}
int main()
{test1();return 0;
}

多线程实现shared_ptr

template<class T>class SharedPtr
{
public:SharedPtr(T* ptr):_ptr(ptr),_pcount(new int(1)),_lock(new std::mutex){}SharedPtr(const SharedPtr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount),_lock(sp._lock){AddCount();}SharedPtr<T>& operator=(const SharedPtr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;_lock = sp._lock;AddCount();}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}void Release(){_lock->lock();bool deleteflag = false;if (--(*_pcount) == 0){std::cout << "delete:" << _ptr << std::endl;delete _ptr;delete _pcount;deleteflag = true;}_lock->unlock();if (deleteflag){delete _lock;}}void AddCount(){_lock->lock();++(*_pcount);_lock->unlock();}T* Get(){return _ptr;}int use_count(){return *_pcount;}~SharedPtr(){Release();}private:T* _ptr;int* _pcount;std::mutex* _lock;
};void test1()
{SharedPtr<int> sp1(new int(1));SharedPtr<int> sp2(sp1);SharedPtr<int> sp4(new int(10));//sp4 = sp1;sp1 = sp4;
}struct Date
{int _year;int _month;int _day;
};void Func(SharedPtr<Date>& sp, size_t n, std::mutex& mtx)
{std::cout << sp.Get() << std::endl;for (size_t i = 0; i < n; i++){SharedPtr<Date> copy(sp);mtx.lock();sp->_year++;sp->_day++;sp->_month++;mtx.unlock();}}
void test2()
{SharedPtr<Date> p(new Date);std::cout << p.Get() << std::endl;const size_t n = 10000;std::mutex mtx;std::thread t1(Func, std::ref(p), n, std::ref(mtx));std::thread t2(Func, std::ref(p), n, std::ref(mtx));t1.join();t2.join();std::cout<<p.use_count()<<std::endl;
}

shared_ptr的循环引用问题

看下面这段代码

std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);n1->_next=n2;
n2->_prev=n1;

在这里插入图片描述
当node2析构时,引用计数变为1,因为还有node1的next指向它,node1析构时,其引用计数变为1,因为还有node2的_prev指向它。为何此时node2 的prev还在呢?因为node2的引用计数不为0,node2的prev要等node2析构才会释放,node2的析构又由node1的next决定。从而造成了循环引用,导致内存泄漏。

解决方案

1.把prev去掉,只保留next。即把双向链表改为单链表
2.weak_ptr

weak_ptr

不支持RAII,专门设计来辅助解决shared_ptr的循环引用问题

在这里插入图片描述
把ListNode类内对象定义为weak_ptr,不增加引用计数。就能完成对应的析构。

自定义删除器

struct Date
{int _year=0;int _month=0;int _day=0;~Date(){}
};
void test4()
{std::shared_ptr<Date> spa(new Date[10]);
}

new[]开空间时编译器会在数组开头多开4个字节,存要调用析构函数的个数。如果delete不加[]就会造成程序崩溃,因为删除位置不对

可调用对象

函数指针,函数对象,lambda表达式都可以。
在这里插入图片描述
这样就不是默认的delete释放,而是调用自定义的delete方法。
在这里插入图片描述
通过智能指针打开文件也会出现错误,因为shared_ptr默认调用的是delete方法,而关闭文件是fclose,因此也必须自定义删除器。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于GA优化的CNN-LSTM-Attention的时间序列回归预测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1卷积神经网络&#xff08;CNN&#xff09;在时间序列中的应用 4.2 长短时记忆网络&#xff08;LSTM&#xff09;处理序列依赖关系 4.3 注意力机制&#xff08;Attention&#xff09; 5…

9.串口通信

串口基本认识 串行接口简称串口&#xff0c;也称串行通信接口或串行通讯接口&#xff08;通常指COM接口&#xff09;&#xff0c;是采用串行通信方 式的扩展接口。串行接口&#xff08;Serial Interface&#xff09;是指数据一位一位地顺序传送。其特点是通信线路简 单&#x…

【网络爬虫】(2) requests模块,案例:网络图片爬取,附Python代码

1. 基本原理 1.1 requests 模块 requests 是 Python 中一个非常流行的 HTTP 客户端库&#xff0c;用于发送所有的 HTTP 请求类型。它基于 urllib&#xff0c;但比 urllib 更易用。 中文文档地址&#xff1a;Requests: 让 HTTP 服务人类 — Requests 2.18.1 文档 &#xff0…

cookie、localStorage、sessionStorage 详解

目录 cookie 是什么&#xff1f; cookie 不可以跨域请求 cookie 的属性 会话cookie & 永久性cookie cookie 禁用 cookie 的应用 sessionStorage 是什么&#xff1f; 失效时间 存储内容的类型 存储的大小 存储的位置 sessionStorage 的应用 localStorage 是什么…

GTC 2024 火线评论:DPU 重构文件存储访问

编者按&#xff1a;英伟达2024 GTC 大会上周在美国加州召开&#xff0c;星辰天合 CTO 王豪迈在大会现场参与了 GPU 与存储相关的最新技术讨论&#xff0c;继上一篇《GTC 2024 火线评论&#xff1a;GPU 的高效存储利用》之后&#xff0c;这是他发回的第二篇评论文章。 上一篇文章…

【ZZULI数据结构实验一】多项式的三则运算

【ZZULI数据结构实验一】多项式的四则运算 ♋ 结构设计♋ 方法声明♋ 方法实现&#x1f407; 定义一个多项式类型并初始化---CreateDataList&#x1f407; 增加节点---Getnewnode&#x1f407; 打印多项式类型的数据-- PrintPoly&#x1f407; 单链表的尾插--Listpush_back&…

Java多线程实战-从零手搓一个简易线程池(一)定义任务等待队列

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️本系列源码仓库&#xff1a;多线程并发编程学习的多个代码片段(github) &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正…

【C语言】结构体

个人主页点这里~ 结构体 一、结构体类型的声明1、结构的声明2、结构体变量的创建和初始化3、声明时的特殊情况4、自引用 二、结构体内存对齐1、对齐规则2、存在内存对齐的原因3、修改默认对齐数 三、结构体传参四、结构体实现位段 一、结构体类型的声明 我们在指针终篇中提到过…

从零自制docker-5-【USER Namespace NETWORK Namespace】

文章目录 USER Namespace代码NETWORK Namespace代码块 USER Namespace 即进程运行在一个新的namespace中&#xff0c;且该namespace中的User ID和Group IDA在该namespace内外可以不同&#xff0c;可以实现在namspace的用户是root但是对应到宿主机并不是root Cloneflags增加一…

3款免费甘特图制作工具的比较和选择指南

GanntProject GanttProject https://www.ganttproject.biz/ 是一款项目管理和调度应用&#xff0c;适用于 Windows、macOS 和 Linux。它易于使用&#xff0c;无需任何设置&#xff0c;适用于个人用户和小型团队。该应用提供任务层次结构和依存关系、里程碑、基准行、Gantt 图表…

AI论文速读 | 具有时间动态的路网语义增强表示学习

论文标题&#xff1a; Semantic-Enhanced Representation Learning for Road Networks with Temporal Dynamics 作者&#xff1a; Yile Chen&#xff08;陈亦乐&#xff09; ; Xiucheng Li&#xff08;李修成&#xff09;; Gao Cong&#xff08;丛高&#xff09; ; Zhifeng Ba…

卓健易控zj-v8.0设备智能控费系统

卓健易控zj-v8.0设备智能控费系统 详细可联系&#xff1a;19138173009 在现今医疗技术日新月异、突飞猛进的时代&#xff0c;我院服务患者的实力与日俱增。随着先进辅助检查设备的不断完善和引进&#xff0c;医生们如同得到了得力助手&#xff0c;能够为患者做出更加精确的诊断…

TCP重传机制详解——04FACK

文章目录 TCP重传机制详解——04FACK什么是FACKFACK的发展为什么要引入FACK实战抓包讲解开启FACK场景&#xff0c;且达到dup ACK门限值开启FACK场景&#xff0c;未达到dup ACK门限值 为什么要淘汰FACK总结REF TCP重传机制详解——04FACK 什么是FACK FACK的全称是forward ackn…

JVM(二)——垃圾回收

三、垃圾回收 1、如何判断对象可以回收 1&#xff09;引用计数法 定义&#xff1a; 在对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一&#xff1b;任何时刻计数器为零的对象就是…

Java面试篇:Redis使用场景问题(缓存穿透,缓存击穿,缓存雪崩,双写一致性,Redis持久化,数据过期策略,数据淘汰策略)

目录 1.缓存穿透解决方案一:缓存空数据解决方案二&#xff1a;布隆过滤器 2.缓存击穿解决方案一:互斥锁解决方案二:设置当前key逻辑过期 3.缓存雪崩1.给不同的Key的TTL添加随机值2.利用Redis集群提高服务的可用性3.给缓存业务添加降级限流策略4.给业务添加多级缓存 4.双写一致性…

MySQL substr函数使用详解

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

景联文科技上新高质量大模型训练数据!

在过去的一年中&#xff0c;人工智能领域呈现出了风起云涌的态势&#xff0c;其中模型架构、训练数据、多模态技术、超长上下文处理以及智能体发展等方面均取得了突飞猛进的发展。 在3月24日举办的2024全球开发者先锋大会的大模型前沿论坛上&#xff0c;上海人工智能实验室的领…

c语言--内存函数的使用(memcpy、memcmp、memset、memmove)

目录 一、memcpy()1.1声明1.2参数1.3返回值1.4memcpy的使用1.5memcpy模拟使用1.6注意 二、memmove()2.1声明2.2参数2.3返回值2.4使用2.5memmove&#xff08;&#xff09;模拟实现 三、memset3.1声明3.2参数3.3返回值3.4使用 四、memcmp()4.1声明4.2参数4.3返回值4.4使用 五、注…

MySQL-extra常见的额外信息

本文为大家介绍MySQL查看执行计划时&#xff0c;extra常见的额外信息 Using index 表示使用了覆盖索引&#xff0c;即通过索引树可以直接获取数据&#xff0c;不需要回表。 表结构: CREATE TABLE t1 (id int(11) NOT NULL AUTO_INCREMENT,name varchar(255) DEFAULT NULL,ag…

IP SSL证书注册流程

使用IP地址申请SSL证书&#xff0c;需要用公网IP地址申请&#xff0c;申请之前确保直接的IP地址可以开放80或者443端口两者选择1个就好&#xff0c;端口不需要一直开放&#xff0c;只要认证的几分钟内开放就可以了&#xff0c;然后IP地址根目录可以上传txt文件。 IP SSL证书认…