c++11 :智能指针

目录

一 为什么需要智能指针?

二 智能指针的使用及原理 

1. RAII

2. auto_ptr

3. unique_ptr

4. shared_ptr

5. weak_ptr

三 内存泄漏

1.什么是内存泄漏,内存泄漏的危害

2. 如何避免内存泄漏?


一 为什么需要智能指针?

🚀为什么需要智能指针? 下面我们先分析一下下面这段程序有没有什么内存方面的问题?

#include <iostream>
using namespace std;int div()
{int a, b;cin >> a >> b;if (b == 0)//抛异常throw invalid_argument("除0错误");return a / b;
}
void f1()
{int* p = new int;cout << div() << endl;delete p;
}
int main()
{//捕异常try{f1();}catch (exception& e){cout << e.what() << endl;}return 0;
}

运行结果

通过上面的程序中我们可以看到,new了以后,而且也delete了,但是因为抛异常有点早,程序执行不到delete的位置,所以就导致内存泄露了,此外如果我们在写代码的过程中忘了释放资源的话也会导致内存泄漏。为了解决上述问题,接下来引入智能指针。

🍉:我们首先写一个类

#pragma oncetemplate<class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){if (_ptr){std::cout << "delete" << _ptr<<std::endl;delete _ptr;}}
private:T* _ptr;
};
#include <iostream>
#include "SmartPtr.h"
using namespace std;int div()
{int a, b;cin >> a >> b;if (b == 0)//抛异常throw invalid_argument("除0错误");return a / b;
}
void f1()
{/// 修改部分,将指针存储在SmartPtr这个类中int* p = new int;SmartPtr<int> sp(p);cout << div() << endl;//delete p;
}
int main()
{//捕异常try{f1();}catch (exception& e){cout << e.what() << endl;}return 0;
}

测试结果: 

上面我们通过创建一个类SmartPtr ,让 SmartPtr sp(p)对p进行管理资源的释放。无论函数正常结束,还是抛异常,都会导致sp对象的生命周期到了以后,调用析构函数~SmartPtr()释放内存。

 上述我们通过类对资源p进行管理,帮我们管理资源的释放,这个类我们就叫智能指针


二 智能指针的使用及原理 

1. RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、互斥量等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的 时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

RAII和智能指针的关系:RAII是一种托管资源的思想,智能指针就是依靠这种RAII实现的。


观察上述代码

void f1()
{/// 修改部分,将指针存储在SmartPtr这个类中int* p = new int;SmartPtr<int> sp(p);cout << div() << endl;//delete p;
}

我们还可以直接这样

SmartPtr<int> sp(new int);/修改后的cout << div() << endl;

 但是这样的话我们又会面临一个问题那就是 如果我们想要访问这个指针变量,我们需要再加以下成员函数。

	T& operator*(){return *_ptr;}T* operator->(){return _ptr;}

这样的话我们就可以访问和修改指针变量了

    SmartPtr<int> sp1(new int);*sp1 = 10;SmartPtr<pair<int, int>> sp2(new pair<int, int>);sp2->first = 20;sp2->second = 30;

 并且会自动释放内存

 以上是智能指针的简单demo,但是上述代码还存在很多问题,我们通过以下代码进行测试

int main()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2 = sp1;//拷贝构造sp2(sp1);return 0;
}

测试结果:

🍎问题分析:我们并没有给SmartPtr构造拷贝构造函数,编译器会自动生成默认拷贝构造即值拷贝(浅拷贝),sp1---------->资源, sp2----------->资源 ,当sp1和sp2出了作用域会对资源进行析构,即sp2先对资源进行析构,sp1又对同一份资源进行了析构,同一份资源不能析构二次,因为第一次析构以及释放了所以出现了报错。

 上述原因就在于浅拷贝造成了报错,但是我们不能说即然浅拷贝有问题,我们进行深拷贝不就行了吗?答案是不可以的,智能指针是用来模拟原生指针的 ,原生指针 p1=p2;就是值拷贝代表着指向同一块空间。

接下来我们引出解决上述问题的三种解决方法:

  1. 管理器转移:c++98 auto_ptr 
  2. 防拷贝:       c++11 unique_ptr 
  3. 引言计数     c++11 shared_ptr  (循环引言的问题,又需要weak_ptr来解决)

2. auto_ptr

我们对auto_ptr的拷贝函数进行构造

//拷贝构造auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}

测试如下:

int main()
{lt::auto_ptr<int> sp1(new int);lt::auto_ptr<int> sp2 = sp1;//拷贝构造sp2(sp1);return 0;
}

为什么只析构一次,并且为什么auto_ptr叫做管理权转移呢? 我们通过下图进行描述

 

 管理权转移:早期c++98设计缺陷,因为它会把其他指针置为空,不建议使用。


3. unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝

		unique_ptr(unique_ptr<T>& up) = delete;unique_ptr<T>& operator==(unique_ptr<T>& up) = delete;

但是unique也有缺陷,如果有需要拷贝的场景,就无法使用。 所以c++11又搞出一个智能shared_ptr.


4. shared_ptr

shared_ptr :是通过引用计数的方式来实现多个shared_ptr对象之间共享资源

  • shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享
  • 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。 3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源; 4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指 针了。 
#pragma oncenamespace lt
{template<class T>class shared_ptr{public:shared_ptr(T* ptr):_ptr(ptr),_pcount(new int(1)){}shared_ptr(shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (this != &sp){if (--*(_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);		}}T& operator*()//const 这里加是为了const this 指针。不能说(const){return *_ptr;}T* operator->(){return _ptr;}~shared_ptr(){if (--(*_pcount)==0 && _ptr){std::cout << "delete" << _ptr << std::endl;delete _ptr;_ptr = nullptr;delete _pcount;_pcount = nullptr;}}private:T* _ptr;int* _pcount;};
}

接下来我们侧重讲一下拷贝构造 

这里有个注意的点就是当计数为1进行拷贝是需要注意 delete _ptr delete _pcount


5. weak_ptr

shared_ptr⼤多数情况下管理资源⾮常合适,⽀持RAII,也⽀持拷⻉。但是在循环引⽤的场景下会导致资源没得到释放内存泄漏,所以我们要认识循环引⽤的场景和资源没释放的原因,并且学会使⽤weak_ptr解决这种问题。

struct listNode
{int _data;shared_ptr<listNode> _next;shared_ptr<listNode> _prev;};

 如图所示:

 

  •   右边的节点什么时候释放呢,左边节点中的_next管着呢,_next析构后,右边的节点就释放了。
  • next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。
  • 左边节点什么时候释放呢,左边节点由右边节点中的_prev管着呢,_prev析构后,左边的节点就释放了。
  • _prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。

此逻辑上成功形成回旋镖似的循环引⽤,谁都不会释放就形成了循环引⽤,导致内存泄漏。

 解决方法:把ListNode结构体中的_next和_prev改成weak_ptr,weak_ptr 不增加它的引⽤计数,就成功打破了循环引⽤,这就解决了这⾥的问题。

🍏weak_ptr 的简单实现 

template<class T>class weak_ptr{public:weak_ptr() = default;weak_ptr(shared_ptr<T>& sp):_ptr(sp.get_ptr()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get_ptr();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}	private:T* _ptr;};
}

weak_ptr严格来说不是智能指针,因为它没有RAII资源管理,weak_ptr是用来专门解决shared_ptr循环引用造成的问题。我们知道ListNode这个结点本身放在智能指针shared_ptr是没有什么问题的,可以很好的对资源ListNode*进行管理,但是就是因为ListNode 结点中还包含 _next ,_prev这就造成了循环引用。我们希望的是ListNode内的指针不参与计数,

所以我们创建了weak_ptr(shared_ptr<T>& sp) ,目的就是希望把_next,_prev指针存储再weak_ptr中不参与计数。

struct ListNode
{int val;lt::weak_ptr<ListNode> _spnext;lt::weak_ptr<ListNode> _spprev;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{lt::shared_ptr<ListNode> spn1(new ListNode);lt::shared_ptr<ListNode> spn2(new ListNode);spn1->_spnext = spn2;//不想参与计数所以我们希望把spn2(shared_ptr<ListNode>)赋值给spn1->_spnext(weak_ptr<ListNode>)//这种操作不计数//所以我们构造了 _spnext为lt::weak_ptr<ListNode>类型,并且构造了weak_ptr<T>& operator=(const shared_ptr<T>& sp)//仅仅把指针拷贝不计数。spn2->_spprev = spn1;return 0;
}

三 内存泄漏

1.什么是内存泄漏,内存泄漏的危害

  •  什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失,⽽是应⽤程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费
  • .内存泄漏的危害:普通程序运⾏⼀会就结束了出现内存泄漏问题也不⼤,进程正常结束,⻚表的映射关系解除,物理内存也可以释放。⻓期运⾏的程序出现内存泄漏,影响很⼤,如操作系统、后台服务、⻓时间运⾏的客⼾端等等,不断出现内存泄漏会导致可⽤内存不断变少,各种功能响应越来越慢,最终卡死

2. 如何避免内存泄漏?

  • 尽量使⽤智能指针来管理资源 
  • 定期使⽤内存泄漏⼯具检测

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

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

相关文章

大模型在直肠癌预测及治疗方案制定中的应用研究

目录 一、引言 1.1 研究背景与意义 1.2 研究目的 1.3 研究方法与创新点 二、大模型技术概述 2.1 大模型的基本原理 2.2 常见大模型类型及特点 2.3 在医疗领域的应用进展 三、直肠癌预测相关数据收集与处理 3.1 数据来源 3.2 数据清洗与预处理 3.3 特征工程 四、大…

VRRP与防火墙双机热备实验

目录 实验一&#xff1a;VRRP负载均衡与故障切换 实验拓扑​编辑一、实验配置步骤 1. 基础网络配置 2. VRRP双组配置 二、关键验证命令 1. 查看VRRP状态 2. 路由表验证 三、流量分析 正常负载均衡场景&#xff1a; 故障切换验证&#xff1a; 实验二&#xff1a;防火…

OpenCV中的SIFT特征提取

文章目录 引言一、SIFT算法概述二、OpenCV中的SIFT实现2.1 基本使用2.1.1 导入库2.1.2 图片预处理2.1.3 创建SIFT检测器2.1.4 检测关键点并计算描述符2.1.5 检测关键点并计算描述符并对关键点可视化2.1.6 印关键点和描述符的形状信息 2.2 参数调优 三、SIFT的优缺点分析3.1 优点…

【信息系统项目管理师】高分论文:论成本管理与采购管理(信用管理系统)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 论文1、规划成本管理2、成本估算3、成本预算4、成本控制论文 2019年1月,我作为项目经理参与了 XX基金管理有限公司信用管理系统项目。该项目成 本1000万,建设期为1年。通过该项目,XX基金管理有限公司在信用…

从边缘到云端,如何通过时序数据库 TDengine 实现数据的全局洞

在当今数字化转型加速的背景下&#xff0c;海量的数据生成和实时处理需求已成为企业面临的关键挑战。无论是物联网设备、工业自动化系统&#xff0c;还是智能城市的各类传感器&#xff0c;数据的采集、传输与分析效率&#xff0c;直接影响企业的决策与运营。为此&#xff0c;TD…

Axure全局变量的含义与基础应用

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:全局变量 主要内容:全局变量含义、基础应用 应用场景:元件赋值 案例展示: 案例视频:

题目 3320: 蓝桥杯2025年第十六届省赛真题-产值调整

题目 3320: 蓝桥杯2025年第十六届省赛真题-产值调整 时间限制: 2s 内存限制: 192MB 提交: 549 解决: 122 题目描述 偏远的小镇上&#xff0c;三兄弟共同经营着一家小型矿业公司 “兄弟矿业”。公司旗下有三座矿山&#xff1a;金矿、银矿和铜矿&#xff0c;它们的初始产值分别用…

常见缓存淘汰算法(LRU、LFU、FIFO)的区别与实现

一、前言 缓存淘汰算法主要用于在内存资源有限的情况下&#xff0c;优化缓存空间的使用效率。以确保缓存系统在容量不足时能够智能地选择需要移除的数据。 二、LRU&#xff08;Least Recently Used&#xff09; 核心思想&#xff1a;淘汰最久未被访问的数据。实现方式&#x…

linux ptrace 图文详解(七) gdb、strace跟踪系统调用

目录 一、gdb/strace 跟踪程序系统调用 二、实现原理 三、代码实现 四、总结 &#xff08;代码&#xff1a;linux 6.3.1&#xff0c;架构&#xff1a;arm64&#xff09; One look is worth a thousand words. —— Tess Flanders 相关链接&#xff1a; linux ptrace 图…

Git基本使用(很详细)

一&#xff1a;Git 概述 1.1 定义&#xff1a;分布式版本控制系统 1.2 版本控制 &#xff08;1&#xff09;定义&#xff1a; 版本控制时一种记录文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统 &#xff08;2&#xff09;举例 多副本 优化&#xff1a; 不使用多…

23种设计模式-结构型模式之桥接模式(Java版本)

Java 桥接模式&#xff08;Bridge Pattern&#xff09;详解 &#x1f309; 什么是桥接模式&#xff1f; 桥接模式用于将抽象部分与实现部分分离&#xff0c;使它们可以独立变化。 通过在两个独立变化的维度之间建立“桥”&#xff0c;避免因多维度扩展导致的类爆炸。 &#x…

基于SIMMECHANICS的单自由度磁悬浮隔振器PID控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 单自由度磁悬浮减振器工作原理简介 4.2 SIMMECHANICS工具箱 5.完整工程文件 1.课题概述 基于SIMMECHANICS的单自由度磁悬浮隔振器PID控制系统simulink建模与仿真。其中&#xff0c;SIMMECHANICS是M…

contenthash 持久化缓存

以下是关于持久化缓存(contenthash)的深度技术解析,涵盖原理、配置策略及最佳实践,帮助我们构建高性能前端应用的缓存体系: 一、缓存机制核心原理 1. 浏览器缓存决策矩阵 触发条件缓存行为对应场景URL 未变化 + 强缓存有效直接读取磁盘/内存缓存未修改的静态资源URL 变化…

【前端记事】关于electron的入门使用

electron入门使用 背景how to start第一步 创建一个vite-vue3项目第二步 装各种依赖第三步 配置vite.config.jspackage.jsonelectron入口 启动重写关闭、隐藏、最大化最小化 背景 最近对electron比较感兴趣&#xff0c;折腾一段时间后有了点眉目&#xff0c;记录一下 how to …

跨浏览器音频录制:实现兼容的音频捕获与WAV格式生成

在现代Web开发中&#xff0c;音频录制功能越来越受到开发者的关注。无论是在线会议、语音识别还是简单的语音留言&#xff0c;音频录制都是一个重要的功能。然而&#xff0c;实现一个跨浏览器的音频录制功能并非易事&#xff0c;因为不同浏览器对音频录制API的支持存在差异。本…

Semantic Kernel也能充当MCP Client

背景 笔者之前&#xff0c;分别写过两篇关于Semantic Kernel&#xff08;下简称SK&#xff09;相关的博客&#xff0c;最近模型上下文协议&#xff08;下称MCP&#xff09;大火&#xff0c;实际上了解过SK的小伙伴&#xff0c;一看到 MCP的一些具体呈现&#xff0c;会发现&…

识别图片内容OCR并重命名文件

在工作场景中&#xff0c;经常出现通过拍摄设备获取图片后&#xff0c;未及时进行有效命名的情况。这些图片中往往包含关键信息&#xff08;如合同编号、产品型号、日期等&#xff09;&#xff0c;需要人工识别并命名&#xff0c;存在以下痛点&#xff1a; 效率低下&#xff1…

【防火墙 pfsense】3 portal

&#xff08;1&#xff09;应该考虑的问题&#xff1a; ->HTTPS 连接的干扰问题&#xff1a;HTTPS 是一种旨在防止恶意第三方截取和篡改流量的协议。但强制门户的工作原理是截取并改变终端用户与网络之间的连接。这对于 HTTP 流量来说不是问题&#xff0c;但使用 HTTPS 加密…

银发科技:AI健康小屋如何破解老龄化困局

随着全球人口老龄化程度的不断加深&#xff0c;如何保障老年人的健康、提升他们的生活质量&#xff0c;成为了社会各界关注的焦点。 在这场应对老龄化挑战的战役中&#xff0c;智绅科技顺势而生&#xff0c;七彩喜智慧养老系统构筑居家养老安全网。 而AI健康小屋作为一项创新…

TCP协议理解

文章目录 TCP协议理解理论基础TCP首部结构图示字段逐项解析 TCP是面向连接&#xff08;Connection-Oriented&#xff09;面向连接的核心表现TCP 面向连接的核心特性TCP 与UDP对比 TCP是一个可靠的(reliable)序号与确认机制&#xff08;Sequencing & Acknowledgment&#xf…