35 智能指针

目录

  1. 为什么需要智能指针?
  2. 内存泄露
  3. 智能指针的使用及原理
  4. c++11和boost中智能指针的关系
  5. RAII扩展学习

1. 为什么需要智能指针?

下面我们先分析一下下面这段程序有没有什么内存方面的问题?

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

上面的程序抛异常后,会导致资源没有释放

2. 内存泄露

2.1 什么是内存泄露,危害

内存泄露指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况,并不是指内存在物理上消失,而是应用程序分配某段内存后,因为设计错误,失去了对这段内存的控制,因而造成了内存的浪费

危害:长期运行的程序出现内存泄露,影响很大,如操作系统、后台服务等等,出现内存泄露导致响应越来越慢,最终卡死

void MemoryLeaks()
{// 1.内存申请了忘记释放int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;// 2.异常安全问题int* p3 = new int[10];Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.delete[] p3;
}

2.2 内存泄露分类

c/c++一般关心两方面的泄露

  • 堆内存泄露(Heap Leak)
    堆内存指程序执行中依据需要分配过malloc/calloc/realloc/new等从堆中分配的一块内存,用完后必须调用对应的free或者delete删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,会产生Heap Leak

  • 系统资源泄露
    指程序使用系统分配的资源,比如套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定

2.3 如何检测内存泄露(了解)

在linux下:linux下几款内存泄漏检测工具
在windows下:VLD工具说明
其他工具:内存泄漏工具比较

2.4 如何避免内存泄露

1.工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态,但是如果碰上异常时,就注意释放了,还是可能会出现问题,需要智能指针来管理才有保证
2.采用RAII思想或者智能指针来管理
3.有些公司内部规范使用内部实现的私有内存管理库,这套库自带内存泄露检测的功能选项
4.出问题了使用内存泄露工具检测。ps:不过很多工具都不够靠谱,收费昂贵

总结一下:
内存泄露工具非常常见,解决方案分为两种:1.事前预防型,如智能指针等 2.事后查错型,如泄露检测工具

3. 智能指针的使用及原理

3.1 RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(内存、文件句柄、网络连接、互斥量等等)的简单技术

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

  • 不需要显式的释放资源
  • 对象所需的资源在其生命周期始终有效
template <class T>
class AutoPtr
{
public:AutoPtr(T* ptr):_ptr(ptr){}~AutoPtr(){if (_ptr){std::cout << "delete ptr" << std::endl;delete _ptr;}}private:T* _ptr;
};

3.2 智能指针的原理

上面的autoptr还不鞥称为智能指针,因为还不具有指针的行为。指针可以解引用,可以通过->访问。所以还需要重载这些

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

总结一下智能指针的原理
1.RAII特性
2.重载*和->,具有指针一样的行为

这个智能指针虽然可以自动释放资源,但当用一个智能指针拷贝另一个,就会崩溃,第二次释放是野指针。看看库的指针解决这个问题是如何发展的

SharedPtr<int> p = new int(5);
SharedPtr<int> p2(p);
SharedPtr<int> p3 = new int(3);
p3 = p;

3.3 std::auto_ptr

std::auto_ptr文档
c++98版本的库中提供了auto_ptr的智能指针,下面展示auto_ptr的使用及问题
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解原理

// C++98  管理权转移  auto_ptrtemplate<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(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;}~auto_ptr(){if (_ptr){std::cout << "delete:" << _ptr << std::endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};// 结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr//int main()//{//  std::auto_ptr<int> sp1(new int);//  std::auto_ptr<int> sp2(sp1); // 管理权转移
////  // sp1悬空
//  *sp2 = 10;//  cout << *sp2 << endl;//  cout << *sp1 << endl;//  return 0;//}

这种会将之前的指针悬空,不熟悉机制的人访问会出错

3.4 std::unique_ptr

c++11提供了更靠谱的unique_ptr
unique_ptr文档
简单粗暴的防止拷贝

// C++11库才更新智能指针实现
// C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr// C++11将boost库中智能指针精华部分吸收了过来
// C++11->unique_ptr/shared_ptr/weak_ptr// unique_ptr/scoped_ptr// 原理:简单粗暴 -- 防拷贝
template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){}if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:T* _ptr;
};

3.5 std::shared_ptr

c++11开始提供更靠谱的并且支持拷贝的shared_ptr
std::shared_ptr文档
shared_ptr的原理:是通过引用计数的方式实现多个shared_ptr对象之间共享资源。例如:老师下课前会让最后走的学生把门锁上
1.shared_ptr在其内部,给每个资源都维护了一个计数,记录该资源被几个对象共享
2.对象拷贝和赋值时,增加一个对象访问资源,计数加一。在对下被销毁时(也就是析构函数调用),说明自己不使用该资源了,对象的引用计数减一
3.如果引用计数是0,说明自己是最后一个使用该资源的对象,必须释放该资源
4.如果不是0,说明除了自己海域其他对象在使用该资源,不能释放该资源,否则其他对象就成野指针了

计数要保证管理同一个资源的指针访问相同的引用计数,得跟着资源走。int显然不行,static会导致所有对象都访问一份,不同资源的指针需要有自己的计数。所以用指针保存计数

template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx){AddRef();}void Release(){_pmtx->lock();bool flag = false;if (--(*_pRefCount) == 0 && _ptr){cout << "delete:" << _ptr << endl;delete _ptr;delete _pRefCount;flag = true;}_pmtx->unlock();if (flag == true){delete _pmtx;}}void AddRef(){_pmtx->lock();++(*_pRefCount);_pmtx->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){//if (this != &sp)if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pRefCount = sp._pRefCount;_pmtx = sp._pmtx;AddRef();}return *this;}int use_count(){return *_pRefCount;}~shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}
private:T* _ptr;int* _pRefCount;mutex* _pmtx;
};

std::shared_ptr的线程安全问题
在多线程章节说明

循环引用问题

上面的基本完美,但还有循环引用的问题

struct ListNode{int _data;shared_ptr<ListNode> _prev;shared_ptr<ListNode> _next;~ListNode(){ cout << "~ListNode()" << endl; }};shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);cout << node1.use_count() << endl;cout << node2.use_count() << endl;node1->_next = node2;node2->_prev = node1;}cout << node1.use_count() << endl;cout << node2.use_count() << endl;

分析:
1.node1和node2两个智能指针对象指向两个节点,引用计数变为1,不需要手动deelte
2.node1的_next指向node2,node2的_prev指向node1,引用计数变为2
3.node1和node2析构,引用计数减到1,但是_next还指向下一个节点,但是_prev还指向上一个节点
4.也就是说_next析构了,node2就释放了
5.也就是说_prev析构了,node1就释放了
6.但是_next属于node的成员,node1释放了,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,这就是循环引用,谁也不会释放

在这里插入图片描述解决方法就是把节点的两个指针换成weak_ptr,原理:node1->_next = node2,这两个步骤是不会增加node1和node2的引用计数

template <class T>
class WeakPtr
{
public:WeakPtr():_ptr(nullptr){}WeakPtr(const SharedPtr<T>& sp):_ptr(sp.get()){}WeakPtr<T>& operator=(const SharedPtr<T>& sp){_ptr = sp.get();return *this;}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

如果不是new出来的是一个数组,[]这种就会出问题。可以设计一个删除器来解决,构造的时候传入一个调用对象,释放的时候调用

template <class D>
SharedPtr(T* ptr, D del): _ptr(ptr),_pcount(new int(1)),_del(del)
{}void Release()
{if (--(*_pcount) == 0){//std::cout << "delete ptr" << std::endl;_del(_ptr);}
}std::function<void(T*)> _del = [](T* ptr) {delete ptr; };
SharedPtr<int> p(new int[5], [](int* ptr) {delete[] ptr; });

4. c++11和boost中智能指针的关系

Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,由Boost社区组织开发、维护。Boost库可以与C++标准库完美共同工作,并且为其提供扩展功能。

1.c++98中产生了第一个智能指针auto_ptr
2.c++boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr
3.c++TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版
4.c++11,引入了unique_ptr和shraed_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost实现的

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

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

相关文章

Ubuntu无法安全地用该源进行更新,所以默认禁用该源。

解决方案 1. 获取并添加缺失的 GPG 公钥 可以使用 apt-key 命令来添加缺失的公钥。根据错误信息&#xff0c;缺失的公钥是 3B4FE6ACC0B21F32。 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F322. 更新软件包列表 添加公钥后&#xff0c;更…

分享一款超火的的发型stable diffusion提示词!

前言 1、女性发型 Tag短发侧刘海高马尾麻花辫甜美卷发半扎发侧分卷发半扎马尾发波浪空气刘海波波头高马尾空气刘海自然波浪卷复古波浪卷发短发齐刘海矮扎丸子头露出额头小波浪刘海披肩卷发英文Short Hair with Side BangsHigh Ponytail BraidSweet CurlsHalf-Up HairSide-Part…

用Python轻松转换Markdown文件为PDF文档

Markdown&#xff0c;以其简洁的语法和易于阅读的特性&#xff0c;成为了许多作家、开发者和学生记录思想、编写教程或撰写报告的首选格式。然而&#xff0c;在分享或打印这些文档时&#xff0c;Markdown的纯文本形式可能无法满足对版式和布局的专业需求。而将Markdown转换为PD…

【经验篇】Spring Data JPA开启批量更新时乐观锁失效问题

乐观锁机制 什么是乐观锁&#xff1f; 乐观锁的基本思想是&#xff0c;认为在大多数情况下&#xff0c;数据访问不会导致冲突。因此&#xff0c;乐观锁允许多个事务同时读取和修改相同的数据&#xff0c;而不进行显式的锁定。在提交事务之前&#xff0c;会检查是否有其他事务…

无线物联网题集

测试一 未来信息产业的发展在由信息网络向 全面感知和 智能应用两个方向拓展、延伸和突破。 各国均把 物联网作为未来信息化战略的重要内容,融合各种信息技术,突破互联网的限制,将物体接入信息网络。 计算机的出现,开始了第四次工业革命,开始了人机物的高度融合&#xff08;&…

实战大数据:分布式大数据分析处理系统的开发与应用

&#x1f482; 个人网站:【 摸鱼游戏】【网址导航】【神级代码资源网站】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

第15届蓝桥杯Python青少组选拔赛(STEMA)2023年8月真题-附答案

第15届蓝桥杯Python青少组选拔赛&#xff08;STEMA&#xff09;2023年8月真题 题目总数&#xff1a; 11 总分数&#xff1a; 400 一、单选题 第 1 题 单选题 以下不符合 Python 语言变量命名规则的是&#xff08; &#xff09;。 A. k B. 2_k C. _k D. ok 答案 B …

VirtualBox 虚拟机的网络通过宿主机的网络进行冲浪

虚拟机与宿主机通过桥接模式处在同一个网络中 1.说明2.操作步骤2.1.虚拟机设置网络2.2.手动指定虚拟机的IP 1.说明 A.虚拟机 ubuntu-20.04 B.宿主机网络 Wireless LAN adapter WLAN:Connection-specific DNS Suffix . : lanIPv4 Address. . . . . . . . . . . : 192.168.111…

超强总结Kafka详解

一、Kafka简介 Kafka是什么 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff08;消息引擎系统&#xff09;&#xff0c;它可以处理消费者在网站中的所有动作流数据。 这种动作&#xff08;网页浏览&#xff0c; 搜索和其他用户的行动&#xff09;是在现代网络上的许多社…

使用elasticsearch完成多语言搜索的三种方式

文档目标&#xff1a; 基于elasticsearch&#xff0c;实现不同语言搜索特定语言的文档数据&#xff1b;比如输入中文的内容&#xff0c;搜索中文文档数据&#xff0c;输入英文搜索英文文档数据&#xff0c;日韩文类似 方案概述&#xff1a; 方式一&#xff1a;不同的语言使用不…

使用Ubuntu 22.04安装Frappe-Bench【二】

系列文章目录 第一章 使用VMware创建Ubuntu 22.04【一】 文章目录 系列文章目录前言什么是Frappe-Bench&#xff1f;使用安装ERPNext能实现什么效果&#xff1f; 官网给了一个说明 一、使用Ubuntu 22.04安装Frappe-Bench一、安装要求二、安装命令三、 可能出现问题 总结 前言 …

【计算机毕业设计】026基于微信小程序的原创音乐

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Linux多进程和多线程(五)进程间通信-消息队列

多进程(五) 进程间通信 消息队列 ftok()函数创建消息队列 创建消息队列示例 msgctl 函数示例:在上⼀个示例的基础上&#xff0c;加上删除队列的代码 发送消息 示例: 接收消息示例 多进程(五) 进程间通信 消息队列 消息队列是一种进程间通信机制&#xff0c;它允许两个或多个…

LCD显示从电路IC 到 驱动编写调试

文章目录 LCD驱动电路IC简述Panel 模块驱动图示含义接口与连接 Panel内部驱动驱动原理框图TCON(Timing Controller):时序控制器。一、控制屏幕时序与信号驱动二、提升图像质量三、接口支持与兼容性四、市场应用广泛 Gate控制信号工作时序Source Driver IC原理框图 LCD驱动应该怎…

JAVA—图形化“登录,注册”界面

前言&#xff1a;学习了一段时间JAVA的swing组件&#xff0c;心血来潮写了一个登录&#xff0c;注册界面。 知道大伙喜欢美女&#xff0c;所以把用户登录界面背景设置成了beauty&#xff01; 所用知识基本上都属于swing组件&#xff1a; javax.siwng.JFrame; //窗体类 javax.sw…

c++习题08-计算星期几

目录 一&#xff0c;问题 二&#xff0c;思路 三&#xff0c;代码 一&#xff0c;问题 二&#xff0c;思路 首先&#xff0c;需要注意到的是3^2000这个数值很大&#xff0c;已经远远超过了long long 数据类型能够表示的范围&#xff0c;如果想要使用指定的数据类型来保存…

14-18 2024 年影响企业 GenAI 的关键技术趋势

现在&#xff0c;大多数 .com 公司已于 2023 年更名为 .ai&#xff0c;那么价值万亿美元的问题是&#xff1a;接下来会发生什么&#xff1f;哪些关键障碍、工具、技术和方法将重塑格局 企业 AI 的不同之处在于&#xff0c;它专注于可衡量、可管理的输出&#xff0c;企业可以控…

前端Web开发HTML5+CSS3+移动web视频教程 Day4 CSS 第2天

P44 - P 四个知识点&#xff1a; 复合选择器 CSS特性 背景属性 显示模式 复合选择器 复合选择器仍然是选择器&#xff0c;只要是选择器&#xff0c;作用就是找标签。复合选择器就是把基础选择器进行组合使用。组合了之后就可以在大量的标签里面更快更精准地找标签了。找…

Qt中线程的使用

目录 1 .QThread重要信号和函数 1.1 常用共用成员函数 1.2信号和槽函数 1.3静态函数 1.4 任务处理函数 2.关于QThread的依附问题&#xff1a; 3.关于connect连接 4.QThread的使用 5.线程池QThreadPool 5.1. 线程池的原理 5.2&#xff0e;QRunable类 5.3. QThreadPoo…

安装维修制氮设备的注意指南

制氮设备在许多工业领域都发挥着重要作用&#xff0c;无论是确保生产过程中的氮气供应&#xff0c;还是维持设备的稳定运行&#xff0c;正确的安装和维修都是关键。以下是一些重要的注意事项&#xff0c;帮助您顺利完成制氮设备的安装与维修工作。 一、安装注意事项 (一)选址与…