【C++】学习笔记——智能指针

文章目录

  • 二十一、智能指针
    • 1. 内存泄漏
    • 2. 智能指针的使用及原理
      • RAII
      • 智能指针的原理
      • auto_ptr
      • unique_ptr
      • shared_ptr
      • shared_ptr的循环引用
      • weak_ptr
      • 删除器
  • 未完待续


二十一、智能指针

1. 内存泄漏

在上一章的异常中,我们了解到如果出现了异常,会中断执行流,跳转到catch处。但是这种情况非常不好,如果我们跳过了内存释放的代码,就会导致内存泄漏。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
虽然我们可以通过异常的再次抛出来解决,但是终究是比较麻烦。

如何避免内存泄露呢?

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。但这只是理想状态,仍有问题出现内存泄漏,需要智能指针来保障。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 使用内存泄漏工具检测。

内存泄漏非常常见,解决方案分为两种:①事前预防型。如智能指针等。②事后查错型。如内存泄漏检测工具。

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

RAII

RAII(Resource Acquisition Is Initialization)是一种 利用对象生命周期来控制程序资源 (如内存、文件句柄、网络连接、互斥量等等)的简单技术。我们可以使用对象来管理资源,在创建对象的时候获取资源,销毁对象的时候释放资源。

#include <iostream>
using namespace std;// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr
{
public:// 构造函数获取资源SmartPtr(T* ptr = nullptr): _ptr(ptr){}// 析构函数释放资源~SmartPtr(){if (_ptr)delete _ptr;cout << "~SmartPtr()" << endl;}
private:T* _ptr;
};int div()
{int a, b;cin >> a >> b;if (b == 0)// 抛出个C++异常标准库里的异常类型throw invalid_argument("除0错误");return a / b;
}void Func()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2(new int);cout << div() << endl;
}int main()
{try{Func();}// 使用异常标准库的基类获取catch (const exception& e){cout << e.what() << endl;}return 0;
}

在这里插入图片描述
我们发现即使出现了异常,也成功把资源给回收了,这种方式就是 RAII 技术。
这种做法有两大好处:①不需要显式地释放资源。②采用这种方式,对象所需的资源在其生命期内始终保持有效。

智能指针的原理

智能指针就是借助的 RAII 思想来实现的。但是上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。还得需要将* 、->重载下,才可让其像指针一样去使用。

#include <iostream>
using namespace std;// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr
{
public:// 构造函数获取资源SmartPtr(T* ptr = nullptr): _ptr(ptr){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// 析构函数释放资源~SmartPtr(){if (_ptr)delete _ptr;cout << "~SmartPtr()" << endl;}
private:T* _ptr;
};int div()
{int a, b;cin >> a >> b;if (b == 0)// 抛出个C++异常标准库里的异常类型throw invalid_argument("除0错误");return a / b;
}void Func()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2(new int);// 使其具有指针的行为*sp1 += 10;SmartPtr<pair<string, int>> sp3(new pair<string, int>);sp3->second = 1;sp3.operator->()->first = "hello";cout << div() << endl;
}int main()
{try{Func();}// 使用异常标准库的基类获取catch (const exception& e){cout << e.what() << endl;}return 0;
}

智能指针的特性:①RAII特性。②重载operator*和opertaor->,具有像指针一样的行为。

auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。需要注意的是,auto_ptr运行拷贝构造和赋值重载,但是 他会把旧的指针置空

#include <iostream>
#include <memory>
using namespace std;int main()
{auto_ptr<int> sp1(new int);auto_ptr<int> sp2(sp1);*sp2 = 10;cout << *sp2 << endl;cout << *sp1 << endl;return 0;
}

在这里插入图片描述
在这里插入图片描述
auto_ptr的模拟实现:

namespace my
{template<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 = nullptr;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

由于auto_ptr的特性,会将旧指针置空,所以一般都不会用这个。

unique_ptr

unique_ptr解决了auto_ptr的缺点,因为unique_ptr直接就是禁止拷贝构造以及复制重载。非常简单粗暴。
unique_ptr的模拟实现:

#include <iostream>
#include <memory>
using namespace std;namespace my
{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;}// delete掉拷贝构造和复制重载unique_ptr(const unique_ptr<T>&sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;private:T* _ptr;};
}

由于unique_ptr的特性,一个资源只能被一个指针所指向。

shared_ptr

unique_ptr虽然解决了auto_ptr的问题,但是限制太大了,如果非要多个指针指向同一块资源的话就没办法,于是C++又提供了新的智能指针——shared_ptr。
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。最后一个指针释放资源。
那我们如何实现这个方法呢?怎么定义引用计数?使用局部变量吗?当然不可以,因为这样会导致每个对象里面都有自己独立的引用计数,失去了意义。静态变量吗?也不行。因为静态会导致类中只能存在1份,即只能对一个资源有效,多个资源就无法通过一个静态变量来管理。
那怎么办?我们可以和智能指针一样,构造时创建一个引用计数,析构时释放引用计数。
shared_ptr模拟实现:

#include <iostream>
#include <memory>
using namespace std;namespace my
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount;// 拷贝构造使引用计数+1++(*_pcount);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){// 自己给自己赋值没意义if (_ptr != sp._ptr){// 使原来的引用计数-1release();_ptr = sp._ptr;_pcount = sp._pcount;// 新的引用计数+1++(*_pcount);}return *this;}// 资源释放void release(){// 引用计数变成0就释放资源if (--(*_pcount) == 0){cout << "delete:" << _ptr << endl;delete _ptr;delete _pcount;}}~shared_ptr(){release();}int use_count(){return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;// 引用计数变量int* _pcount;};
}

shared_ptr的循环引用

根据上面来看,shread_ptr似乎以及非常完善了,真的是这样吗?我们来看看下面这个场景:

struct ListNode
{int _data;shared_ptr<ListNode> _prev;shared_ptr<ListNode> _next;ListNode(int data = 0):_data(data){}~ListNode(){cout << "~ListNode()" << endl;}
};int main()
{shared_ptr<ListNode> node1(new ListNode(10));shared_ptr<ListNode> node2(new ListNode(20));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;
}

在这里插入图片描述
我们发现引用数没错,但是并没有释放资源。这是为什么呢?因为node1和node2析构时,引用计数-1,但是分别还有node1->next以及node2->prev还指向两个节点,因此引用计数并没有变成 0。引用计数不是0就不会析构释放资源,这就是shared_ptr的循环引用问题。
在这里插入图片描述

weak_ptr

上面的shared_ptr循环引用的问题可以使用weak_ptr解决。weak_ptr并不会增加引用计数。并不是全部替换,节点本身都还是shread_ptr,但是节点的前驱指针和后继指针改成了weak_ptr。

#include <iostream>
#include <memory>
using namespace std;struct ListNode
{int _data;// 这里替换成不会增加引用计数的 weak_ptrweak_ptr<ListNode> _prev;weak_ptr<ListNode> _next;ListNode(int data = 0):_data(data){}~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;
}

在这里插入图片描述
这样,只有被shared_ptr指向的节点才会增加引用计数。

删除器

智能指针的释放都是使用 delete 来释放的,与 delete 匹配的是 new,如果不是new出来的对象如何通过智能指针管理呢?比如malloc,或者new[]等等,这样的若是使用delete来释放资源就会出现大问题!该怎么办呢?其实shared_ptr设计了一个删除器来解决这个问题。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <memory>
using namespace std;// 仿函数的删除器
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<int> sp4(new int[10], [](int* p){delete[] p; });std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p){fclose(p); });return 0;
}

在这里插入图片描述
只要在定义的时候在后面跟上删除器(删除的方式)就可以使用了。


未完待续

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

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

相关文章

LocalDateTime计算两个时间之间的间隔

LocalDateTime计算两个时间之间的间隔 嘚吧嘚LocalDateTimeLocalDateLocalTime 嘚吧嘚 自从认识了LocalDateTime之后&#xff0c;使用的频率越来越高了&#xff0c;使用多了就不可避免的涉及到日期的比较、加减以及计算日期间隔这些操作。 但是我发现自己好像不会&#x1f605…

2024年钉钉杯大学生大数据挑战赛倒计时,最后冲刺

2024第三届钉钉杯大学生大数据挑战赛倒计时&#xff0c;小编给大家带来非常实用的最后冲刺助力【A题】&#xff0c;&#xff08;看图资料预览&#xff09;&#xff1a; 中国烟草行业作为国家税收和财政收入的重要支柱&#xff0c;近年来销售收入持续增长。国家对此实行严格的专…

一键测量仪,能否彻底解决燃气灶配件缺陷问题?

燃气灶配件是指用于燃气灶的附件或零部件&#xff0c;用于安装、维护或改进燃气灶的功能和性能。这些配件通常包括各种零部件、附件和替换件&#xff0c;以确保燃气灶的正常运行和安全使用。燃气灶的火焰头是产生火焰的部件&#xff0c;通常根据不同的燃气类型和火力需求选择合…

ETL数据集成丨快速将MySQL数据迁移至Doris数据库

随着大数据技术的迅速发展&#xff0c;越来越多的企业开始寻求高效、灵活的数据存储与分析解决方案。Apache Doris&#xff08;原名 Palo&#xff09;作为一款高性能的MPP&#xff08;大规模并行处理&#xff09;分析型数据库&#xff0c;凭借其在OLAP场景下的卓越表现&#xf…

Minio多主机分布式 docker-compose 集群部署

参考 docker-compose搭建多主机分布式minio - 会bk的鱼 - 博客园 (cnblogs.com) 【运维】docker-compose安装minio集群-CSDN博客 Minio 是个基于 Golang 编写的开源对象存储套件&#xff0c;虽然轻量&#xff0c;却拥有着不错的性能 中文地址&#xff1a;MinIO | 用于AI的S3 …

SYD88xx代码复位不成功和解决办法

原来的复位代码如下: void ota_manage(void){#ifdef _OTA_if(ota_state){switch(ota_state){case 1 : #if defined(_DEBUG_) || defined(_SYD_RTT_DEBUG_)dbg_printf("start FwErase\r\n");#endifCmdFwErase();#if defined(_DEBUG_) || defined(_SYD_RTT_DEBUG_)db…

Spring Boot 动态数据源

目录 前言 前置环境 pom yml Entity Dao 枚举类 数据源 AOP Controller 启动类 演示 前言 大多数系统中&#xff0c;都需要数据库来持久化数据&#xff0c;在大多数情况下&#xff0c;一个系统只需要配置一个数据源便能够完成所有业务的查询&#xff0c;保存操作。…

为什么Transformer需要进行 Multi-head Attention?

目录 1. 前言 2. 基本概念 2.1. Word2Vec 2.2. Attention is all you need 2.3. Self-attention 2.3.1. 概述self-attention 2.3.2. 训练细节 2.4. Multi-head Attention 2.4.1. 多头理论细节 2.4.2. 多头代码实现 2.5. 总结 3. 讨论观点 3.1. 观点1&#xff1a; …

【工具插件类教学】vHierarchy 2工具编辑器扩展使用

目录 一、下载导入 二、使用介绍 1.便捷小工具 a.图标和颜色Icons and colors b.对象组件缩略图Component minimap c.层级线展示Hierarchy lines d.极简模式Minimal mode e.斑马条纹图案Zebra striping f.激活切换Activation toggle 2、快捷键 一、下载导入 资源官方…

二维码门楼牌管理应用平台建设:流程优化与全面考量

文章目录 前言一、工作流程优化&#xff1a;移动端采集与实时更新二、数据完整性与准确性保障三、效率提升与成本节约四、扩展性与未来发展五、数据安全与隐私保护六、用户培训与技术支持 前言 随着智慧城市建设的不断深入&#xff0c;二维码门楼牌管理应用平台作为城市管理的…

数据库事务处理技术——故障恢复

1. 数据故障恢复的宏观思路 我们知道DBMS是利用内存&#xff08;主存&#xff09;和外存&#xff08;辅存&#xff09;这样的存储体系进行数据库的管理&#xff0c;其中内存也就是我们常说的缓存是易失的。而事务时DBMS对数据库进行控制的基本单元&#xff0c;宏观上是由程序设…

算法训练.

一.奶牛晒衣服 题解&#xff1a; 这应该是个二分题&#xff0c;但是我用的是贪心暴力写的&#xff0c;思想就是循坏每次都让湿度最高的使用一次烘衣机&#xff0c;要是湿度最高的可以在自然条件下都能晒干就结束循环&#xff0c;这样内部我第一想法就是每次都排个降序&#xf…

windows下在线预览服务kkFileView4.4.0问题记录

前几天找到一个开源项目&#xff1a;kkFileView&#xff0c;感觉可能以后可能会用到&#xff0c;所以尝试了下。 通过git下载下来&#xff0c;版本是4.4.0&#xff0c;通过idea打开项目&#xff0c;发现老是无法找到组件aspose-cad&#xff0c;版本是23.9. 找了好多文章&#x…

AI学习(1)软件的选择,cuda和pytorch的安装

文章目录 1.使用VScode开发&#xff0c;结合anaconda配置python环境2.安装pytorch库3.深度学习相关的库1.numpy&#xff08;科学计算库&#xff09;2.pandas(数据分析处理库)3.matplotlib&#xff08;可视化库&#xff09;4.seaborn&#xff08;可视化库&#xff09; 1.使用VSc…

Docker三大基础组件

Docker有三个重要的概念&#xff1a;仓库、镜像和容器 &#xff0c;它们是Docker的三大基础组件&#xff0c;这三个组件共同构成了Docker的核心架构&#xff0c;使得Docker能够实现对应用程序的便捷打包、分发和运行。 Docker使用客户端-服务器体系结构。Docker客户端与Docker守…

安装jdk和tomcat

安装nodejs 1.安装nodejs&#xff0c;这是一个jdk一样的软件运行环境 yum -y list installed|grep epel yum -y install nodejs node -v 2.下载对应的nodejs软件npm yum -y install npm npm -v npm set config .....淘宝镜像 3.安装vue/cli command line interface 命令行接…

【Qt】QDateTimeEdit

在Qt中&#xff0c;QDateEdit是用于选择日期的微调框&#xff0c;QTimeEdit是用于选择小时和分钟的微调框 QDateTimeEdit则是基于QDateEdit和QTimeEdit的组合控件&#xff0c;能够同时显示日期和时间&#xff0c;并允许用户以交互方式编辑日期 常用属性 属性说明dateTime时间…

SpringBoot SseEmitter,服务器单项消息推送

防止推送消息乱码 import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.servlet.mvc.method…

每日OJ_牛客HJ74 参数解析

目录 牛客HJ74 参数解析 解析代码1 解析代码2 牛客HJ74 参数解析 参数解析_牛客题霸_牛客网 解析代码1 本题通过以空格和双引号为间隔&#xff0c;统计参数个数。对于双引号&#xff0c;通过添加flag&#xff0c;保证双引号中的空格被输出。 #include <iostream> #i…