【hello C++】智能指针

目录

一、内存泄漏

1.1 什么是内存泄漏,内存泄漏的危害

1.2 内存泄漏分类

1.3 如何检测内存泄漏

1.4 如何避免内存泄漏

二、智能指针的使用及原理

2.1 RAII

2.2 智能指针的原理

2.3 智能指针的发展历程

2.4 智能指针的模拟及实现

三、shared_ptr 常见的问题

3.1 线程安全问题

3.2 循环引用问题



 C++智能指针~🌷

一、内存泄漏

1.1 什么是内存泄漏,内存泄漏的危害

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

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,

出现内存泄漏会导致响应越来越慢,最终卡死。

1.2 内存泄漏分类

C/C++ 程序中一般我们关心两种方面的内存泄漏:
  • 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过 malloc / calloc / realloc / new 等从堆中分配的一
块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生 Heap Leak
  • 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

1.3 如何检测内存泄漏

  • linux下内存泄漏检测:Linux下几款内存泄露检测工具
  • windows下使用第三方工具:VLD工具说明
  • 其他工具:内存泄漏工具比较

1.4 如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
内存泄漏非常常见,解决方案分为两种:
  1. 事前预防型。如:良好的代码风格、智能指针等。
  2. 事后查错型。如泄漏检测工具。

二、智能指针的使用及原理

2.1 RAII

RAII Resource Acquisition Is Initialization )是一种 利用对象生命周期来控制程序资源 (如
内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源 ,接着控制对资源的访问使之在对象的生命周期内始终保持有效,
后在 对象析构的时候释放资源 。借此,我们实际上把管理一份资源的责任托管给了一个对
象。这种做法有两大好处:
  • 不需要显式地释放资源;
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效;

2.2 智能指针的原理

  1. RAII特性;
  2. 重载operator*opertaor->,具有像指针一样的行为;

2.3 智能指针的发展历程

  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 和 shared_ptr 和 weak_ptr。需要注意的是 unique_ptr 对应boost 的 scoped_ptr。并且这些智能指针的实现原理是参考 boost 中的实现的。

2.4 智能指针的模拟及实现

auto_ptr

std::auto_ptr 文档

C++98  版本的库中就提供了  auto_ptr  的智能指针。下面演示的  auto_ptr  的使用及问题。
auto_ptr 的实现原理 :管理权转移的思想,下面简化模拟实现了一份 rxy ::auto_ptr  来了解它
的原理。
// C++98
// auto_ptr;
namespace rxy
{template<class T>class auto_ptr{public:// 构造函数auto_ptr(T* ptr = nullptr):_ptr(ptr){}// 拷贝构造函数auto_ptr(auto_ptr<T>& ptr):_ptr(ptr._ptr){ptr._ptr = nullptr;}// 赋值auto_ptr<T>& operator=(auto_ptr<T>& ptr){if (this != &ptr){if (_ptr)delete _ptr;_ptr = ptr._ptr;ptr._ptr = nullptr;}return *this;}// 析构函数~auto_ptr(){if (_ptr)delete _ptr;}// 像指针一样去使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

总结:

auto_ptr:资源转移,会造成原指针悬空的问题,是一个失败设计,很多公司明确要求不能
使用 auto_ptr。

unique_ptr

unique_ptr 文档

C++11中开始提供更靠谱的unique_ptr
unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的
原理
// unique_ptr
namespace rxy
{template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(unique_ptr<T>& ptr) = delete;unique_ptr<T>& operator=(unique_ptr<T>& ptr) = delete;~unique_ptr(){if (_ptr) delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){retrun _ptr;}private:T* _ptr;};
}

shared_ptr

shared_ptr 文档
C++11中开始提供更靠谱的并且支持拷贝的 shared_ptr;
shared_ptr的原理:是通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源。
  1. shared_ptr 在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
// shared_ptr
// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
#include <iostream>
#include <mutex>
using namespace std;namespace rxy
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_count(new int(1)),_mtx(new mutex){}shared_ptr(const shared_ptr<T>& ptr):_ptr(ptr._ptr), _count(ptr._count), _mtx(ptr._mtx){AddCount();}void AddCount(){_mtx->lock();(*_count)++;_mtx->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& ptr){if (_ptr != ptr._ptr){if (_ptr) Release();_ptr = ptr._ptr;_count = ptr._count;_mtx = ptr._mtx;AddCount();}return *this;}void Release(){bool flag = false;_mtx->lock();if (_ptr && --(*_count) == 0){delete _ptr;delete _count;flag = true;}_mtx->unlock();if (flag == true)delete _mtx;}~shared_ptr(){if (_ptr) Release();}int GetCount(){return *_count;}T* Get() const {return _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _count;mutex* _mtx;};
}

weak_ptr

	// 简化版的weak_ptrtemplate<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& ptr):_ptr(ptr.Get()){}weak_ptr<T>& operator=(shared_ptr<T>& ptr){// 调用shared_ptr的_ptr = ptr.Get();}T* operator->(){return _ptr;}T& operator*(){retrun* _ptr;}private:T* _ptr;};

三、shared_ptr 常见的问题

3.1 线程安全问题

shared_ptr  的线程安全分为两方面:
1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同
时 ++ 或  --  ,这个操作不是原子的,引用计数原来是 1 ++ 了两次,可能还是 2. 这样引用计数
就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++ -- 是需要
加锁的,也就是说引用计数的操作是线程安全的。
2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

3.2 循环引用问题

#include <iostream>
using namespace std;struct ListNode
{int _data;shared_ptr<ListNode> _prev;shared_ptr<ListNode> _next;
};int main()
{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;return 0;
}
循环引用分析:
  1. node1node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动 delete
  2. node1_next指向node2node2_prev指向node1,引用计数变成2
  3. node1node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上 一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1_prev管理,_prev 属于node2成员,所以这就叫循环引用,谁也不会释放。
// 解决方案:
//     在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是:
//    node1->_next = node2;
//    和node2->_prev = node1;时
//    weak_ptr的_next和_prev不会增加node1和node2的引用计数。struct ListNode
{int _data;weak_ptr<ListNode> _prev;weak_ptr<ListNode> _next;~ListNode() { cout << "~ListNode()" << endl; }
};int main()
{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;return 0;
}
如果不是 new 出来的对象如何通过智能指针管理呢?
其实 shared_ptr 设计了一个删除器来解决这 个问题
// 仿函数的删除器
template<class T>
struct FreeFunc 
{void operator()(T* ptr){cout << "free:" << ptr << endl;free(ptr);}
};template<class T>
struct DeleteArrayFunc 
{void operator()(T* ptr){cout << "delete[]" << ptr << endl;delete[] ptr;}
};int main()
{FreeFunc<int> freeFunc;std::shared_ptr<int> sp1((int*)malloc(4), freeFunc);DeleteArrayFunc<int> deleteArrayFunc;std::shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);std::shared_ptr<A> sp4(new A[10], [](A* p) {delete[] p; });std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p){fclose(p); });return 0;
}

坚持打卡😃

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

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

相关文章

如何在页面中嵌入音频和视频?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 嵌入音频⭐ 嵌入视频⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏…

在Ubuntu中使用Docker启动MySQL8的天坑

写在前面 简介&#xff1a; lower_case_table_names 是mysql设置大小写是否敏感的一个参数。 1.参数说明&#xff1a; lower_case_table_names0 表名存储为给定的大小和比较是区分大小写的 lower_case_table_names 1 表名存储在磁盘是小写的&#xff0c;但是比较的时候是不区…

Scractch3.0_Arduino_ESP32_学习随记_WIFI一键配网web(五)

WIFI一键配网web 目的器材程序联系我们 目的 使用手机为C02一键配网 器材 硬件: 齐护机器人C02 购买地址 软件: scratch3.0 下载地址:官网下载 程序 程序如下图所示: 当程序上载完成后使用手机连接WIFI名为qdprobot的网络,密码为12345678。连接后会自动弹出配网的网页。…

[保研/考研机试] KY110 Prime Number 上海交通大学复试上机题 C++实现

题目链接&#xff1a; Prime Numberhttps://www.nowcoder.com/share/jump/437195121691717713466 描述 Output the k-th prime number. 输入描述&#xff1a; k≤10000 输出描述&#xff1a; The k-th prime number. 示例1 输入&#xff1a; 3 7 输出&#xff1a; …

mac电脑 node 基本操作命令

1. 查看node的版本 node -v2. 查看可安装的node版本 sudo npm view node versions3. 安装指定版本的node sudo n 18.9.04. 安装最新版本node sudo n latest5. 安装最新稳定版 sudo n stable6. 清楚node缓存 sudo npm cache clean -f7. 列举已经安装的node版本 n ls 8. 在…

AcWing算法提高课-4.2.3一个简单的整数问题2

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 本题链接&#xff08;AcWing&#xff09; 点这里 题目描述 给定一个长度为 N N N 的数列 A A A&#xff0c;以及 M M M 条指令&#xff0c;每条指令可能是以下两种之一&#xff1a; C l r…

计算机基础之RAID技术

概述 RAID&#xff0c;Redundant Array of Independent Disks&#xff0c;独立磁盘冗余阵列&#xff0c;一种把多块独立的硬盘&#xff08;物理硬盘&#xff09;按不同的方式组合起来形成一个硬盘组&#xff08;逻辑硬盘&#xff09;&#xff0c;从而提供比单个硬盘更高的存储…

Azure通过自动化账户实现对资源变更

Azure通过自动化账户实现对资源变更 创建一个自动化账户第一种方式 添加凭据&#xff08;有更改资源权限的账户&#xff0c;没有auth认证情况&#xff09;创建一个Runbook&#xff0c;测试修改 AnalysisServices 定价层设置定时任务&#xff1a;开始定时任务&#xff1a; 第二种…

01_Hudi 框架概述、数据湖Data Lake、什么是数据湖、数据湖框架、背景概述、Hudi 介绍、Hudi 发展及特性等

本文来自"黑马程序员"hudi课程 1.第一章 Hudi 框架概述 1.1 数据湖Data Lake 1.1.1 仓库和湖泊 1.1.2 什么是数据湖 1.1.3 数据湖的优点 1.1.4 Data Lake vs Data warehouse 1.1.5 数据湖框架 1.1.5.1 Delta Lake 1.1.5.2 Apache Iceberg 1.1.5.3 Apache Hudi 1.1.6…

深入探索 Spring MVC:构建优雅的Web应用

文章目录 前言一、什么是 Spring MVC1.1 什么是 MVC1.2 什么是 Spring MVC 二、Spring MVC 项目的创建2.1 项目的创建2.2 第一个 Spring MVC 程序 —— Hello World 三、RequestMapping 注解3.1 常用属性3.2 方法级别和类级别注解3.3 GetMapping、PostMapping、PutMapping、Del…

css flex 上下结构布局

display: flex; flex-flow: column; justify-content: space-between;

上传图片视频

分布式文件系统MinIo MinIO提供多个语言版本SDK的支持&#xff0c;下边找到java版本的文档&#xff1a; 地址&#xff1a;https://docs.min.io/docs/java-client-quickstart-guide.html MinIO测试&#xff08;上传、删除、下载&#xff09; public class MinioTest {MinioC…