当前位置: 首页 > news >正文

C++智能指针详解

一、智能指针简介

        智能指针是一个类似于指针的类,将指针交给这个类对象进行管理,我们就可以像使用指针一样使用这个类,并且它会自动释放资源。

        智能指针运用了 RAII 的思想(资源获得即初始化)。RAII 是指,用对象的生命周期来管理资源,类对象创建时拿到资源,析构时释放资源。

RAII 优点:

        1、不需要显式释放资源。

        2、在对象生命周期内,资源始终都是有效的。

简单的智能指针的示例:

template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}// 像指针一样使用,重载 * 和 ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// 析构时释放资源~SmartPtr(){cout << "释放资源\n";if (_ptr)delete _ptr;}private:T* _ptr;
};

void func()
{int* p1 = new int[10];int* p2 = new int[10];int* p3 = new int[10];delete[] p1;delete[] p2;delete[] p3;
}

     在上述代码中,指针 p1, p2, p3 在创建时都有可能出现异常,如果在 p1 创建时出现异常,那么我们只需要捕获;如果在 p2 创建时出现异常,那么我们除了捕获异常,还需要释放 p1;而如果在 p3 创建时出现异常,那么我们又要释放 p1 和 p2。

        要写多个 try catch,这会让我们的代码变得十分复杂,并且可能会有遗漏,造成内存泄漏。

        这时,智能指针的优势就体现出来了,只需要把指针交给智能指针进行管理,就能够在生命周期结束时自动释放。

用上面的简单的智能指针示例

void func()
{SmartPtr<int> sp1(new int[10]);SmartPtr<int> sp2(new int[10]);SmartPtr<int> sp3(new int[10]);
}

        在对象的生命周期结束后,会自动调用析构释放资源。我们就不需要写复杂的代码,也不用担心内存泄漏的问题了。

二、智能指针的拷贝问题

        智能指针的拷贝如果不写的话,默认生成的是浅拷贝。而浅拷贝会使同一份资源释放两次,运行会报错。

void func()
{SmartPtr<int> sp1(new int[10]);SmartPtr<int> sp2(sp1);
}

这时候就有多种解决方案:

        1、auto_ptr

        将资源全部转给一方,将另一方置为空。(不靠谱,现在禁止使用了)

        2、unique_ptr

        拷贝有问题,干脆禁止拷贝。将拷贝封住,就可以了。(不需要拷贝的场景)

unique_ptr 的简单实现:

template<class T>
class Unique_Ptr
{
public:Unique_Ptr(T* ptr):_ptr(ptr){}// 像指针一样使用,重载 * 和 ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// 析构时释放资源~Unique_Ptr(){cout << "释放资源\n";if (_ptr)delete _ptr;}Unique_Ptr(const Unique_Ptr<T>& up) = delete;Unique_Ptr<T>& operator=(const Unique_Ptr<T>& up) = delete;
private:T* _ptr;
};

3、shared_ptr

        通过引用计数解决多次析构问题

        用一个引用计数表示当前共有多少对象在使用该指针,每次析构都减引用计数,当引用计数减到0,就释放资源。

        为什么引用计数不能为 int 和 静态 static int ?

        int:如果引用计数是 int ,当我们改了一个引用计数,其他的对象无法同步。

        如:有三个对象 sp1, sp2, sp3,如果sp3拷贝sp2,无法告知sp1,sp1 无法同步引用计数。

        static int:如果用静态的,整个类共用一个引用计数,无法区分shared_ptr 管理的多个指针的引用计数。

        如:sp1(new int(1)); sp2(new int(2)); sp1 和 sp2 的引用计数肯定是不同的,但用静态无法区分,因为它是整个类共有的。

因此,引用计数用指针或引用最佳。

shared_ptr 简单实现代码

template<class T>
class Shared_Ptr
{
public:// 引用计数初始为 1Shared_Ptr(T* ptr):_ptr(ptr),_count(new int(1)){}// 像指针一样使用,重载 * 和 ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// 返回引用计数int use_count(){return *_count;}Shared_Ptr(const Shared_Ptr<T>& sp){// 将资源拷贝过来,并 ++引用计数_ptr = sp._ptr;_count = sp._count;++(*_count);}Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp){// 防自己给自己赋值if (sp._ptr != _ptr){// 赋值会将原本的资源覆盖,因此要进行判断// 如果是最后一个对象,就析构释放,不是就 减减当前计数if (--(*_count) == 0){delete _ptr;delete _count;}// 拷贝资源,++拷贝的计数_ptr = sp._ptr;_count = sp._count;++(*_count);}return *this;}// 析构时释放资源~Shared_Ptr(){// 当引用计数减到 0,就释放资源if (--(*_count) == 0){cout << "释放资源\n";delete _ptr;delete _count;}else{// 打印调试cout << "减减引用计数,当前引用计数为: " << *_count << endl;}}
private:T* _ptr; // 指针int* _count; // 引用计数
};

        上述代码中存在线程安全问题,引用计数需要加锁保护!

多线程测试代码 测试记得把打印的调试信息注释掉

// 测试线程安全:拷贝 n 个对象
// 测试记得把打印的调试信息注释掉
void ThreadRoute(Shared_Ptr<int>& sp, int n, mutex& mtx)
{for (int i = 0; i < n; ++i){Shared_Ptr<int> test(sp);}
}void TestSharedThreadSafe()
{Shared_Ptr<int> sp(new int(1));mutex mtx;int n = 10000;// 因为不清楚内部实现,多线程的引用要使用库函数 refthread t1(ThreadRoute, ref(sp), n, ref(mtx));thread t2(ThreadRoute, ref(sp), n, ref(mtx));t1.join();t2.join();}

        多线程版 shared_ptr 实现,在修改引用计数时,加锁保护

// 多线程
template<class T>
class Shared_Ptr
{
public:// 引用计数初始为 1Shared_Ptr(T* ptr):_ptr(ptr), _count(new int(1)), _pmtx(new mutex){}// 像指针一样使用,重载 * 和 ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}void AddCount(){// 对公共资源 引用计数加加,加锁保护unique_lock<mutex> lock(*_pmtx);++(*_count);}void DelCount(){// 对公共资源 引用计数减减,加锁保护unique_lock<mutex> lock(*_pmtx);--(*_count);}// 返回管理的指针T* Get(){return _ptr;}// 返回引用计数 int use_count(){return *_count;}// 拷贝构造Shared_Ptr(const Shared_Ptr<T>& sp){// 将资源拷贝过来,并 ++引用计数_ptr = sp._ptr;_count = sp._count;_pmtx = sp._pmtx;// 将锁拿到后再 用锁保护,++引用计数AddCount();}Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp){// 防自己给自己赋值if (sp._ptr != _ptr){// 释放原本资源release();// 拷贝资源,++拷贝的计数_pmtx = sp._pmtx;_ptr = sp._ptr;_count = sp._count;AddCount();}return *this;}// 释放资源void release(){unique_lock<mutex> lock(*_pmtx);// 当引用计数减到 0,就释放资源if (--(*_count) == 0){// cout << "释放资源\n";// 释放锁之前 解锁lock.unlock();delete _ptr;delete _count;delete _pmtx;}else{// 打印调试// cout << "减减引用计数,当前引用计数为: " << *_count << endl;}}// 析构时释放资源~Shared_Ptr(){release();}
private:T* _ptr; // 指针int* _count; // 引用计数mutex* _pmtx; // 锁
};

三、shared_ptr 的循环引用问题

        当存在类里面有智能指针互相指向时,就会出现循环引用问题。

 

        因此,官方给 shared_ptr 配了一个小弟:weak_ptr

        weak_ptr 不是常规的智能指针,它具有以下特点

  • 它不支持 RAII

  • 支持像指针一样使用

  • 专门设计出来解决循环引用问题

        核心:weak_ptr 支持用 shared_ptr 构造,它不会加加引用计数。

        测试循环引用的代码:

struct ListNode
{// 双向链表Shared_Ptr<ListNode> _prev;Shared_Ptr<ListNode> _next;// 析构~ListNode(){cout << "释放节点\n";}
};void CirculaReferenceProblem()
{Shared_Ptr<ListNode> n1(new ListNode);Shared_Ptr<ListNode> n2(new ListNode);n1->_next = n2;n2->_prev = n1;
}

        weak_ptr 的简单实现

template<class T>
class Weak_Ptr
{
public:Weak_Ptr():_ptr(nullptr){}Weak_Ptr(const Shared_Ptr<T>& sp):_ptr(sp.Get()){}Weak_Ptr<T>& operator=(const Shared_Ptr<T>& sp){_ptr = sp.Get();return *this;}// 像指针一样使用,重载 * 和 ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};

        解决方案:在内部的互相引用处,用 weak_ptr 就可以了

struct ListNode
{// 在内部的互相引用处,用 weak_ptr 就可以了Weak_Ptr<ListNode> _prev;Weak_Ptr<ListNode> _next;~ListNode(){cout << "释放节点\n";}
};void CirculaReferenceProblem()
{Shared_Ptr<ListNode> n1(new ListNode);Shared_Ptr<ListNode> n2(new ListNode);n1->_next = n2;n2->_prev = n1;
}

四、定制删除器

        有的时候我们使用 new [] 开辟空间或传入的是文件指针,就可以定制删除器来指定使用 delete [] 或fclose() 删除。

        定制删除器就是传入一个可调用对象(仿函数或lambda或函数指针),在释放时调用。

        改变:

        1、成员加一个 function 包装的删除器,构造函数添加删除器模版

        2、release() 中删除改为用定制删除器删除

添加定制删除器

template<class T>
class Shared_Ptr
{
public:Shared_Ptr():_ptr(nullptr), _count(new int(1)), _pmtx(new mutex){}// 引用计数初始为 1Shared_Ptr(T* ptr):_ptr(ptr), _count(new int(1)), _pmtx(new mutex){}// 定制删除器template<class D>Shared_Ptr(T* ptr, D del):_ptr(ptr), _count(new int(1)), _pmtx(new mutex), _del(del){}// 像指针一样使用,重载 * 和 ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}void AddCount(){// 对公共资源 引用计数加加,加锁保护unique_lock<mutex> lock(*_pmtx);++(*_count);}void DelCount(){// 对公共资源 引用计数减减,加锁保护unique_lock<mutex> lock(*_pmtx);--(*_count);}// 返回管理的指针T* Get() const{return _ptr;}// 返回引用计数 int use_count(){return *_count;}// 拷贝构造Shared_Ptr(const Shared_Ptr<T>& sp){// 将资源拷贝过来,并 ++引用计数_ptr = sp._ptr;_count = sp._count;_pmtx = sp._pmtx;// 将锁拿到后再 用锁保护,++引用计数AddCount();}Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp){// 防自己给自己赋值if (sp._ptr != _ptr){// 释放原本资源release();// 拷贝资源,++拷贝的计数_pmtx = sp._pmtx;_ptr = sp._ptr;_count = sp._count;AddCount();}return *this;}// 释放资源void release(){unique_lock<mutex> lock(*_pmtx);// 当引用计数减到 0,就释放资源if (--(*_count) == 0){// cout << "释放资源\n";// 释放锁之前 解锁lock.unlock();// delete _ptr;// 改为用定制删除器删除_del(_ptr);delete _count;delete _pmtx;}else{// 打印调试// cout << "减减引用计数,当前引用计数为: " << *_count << endl;}}// 析构时释放资源~Shared_Ptr(){release();}
private:T* _ptr; // 指针int* _count; // 引用计数mutex* _pmtx; // 锁function<void(T*)> _del = [](T* ptr) {cout << "默认 delete\n";delete ptr;};
};

  测试代码

template<class T>
struct DeleteArr
{void operator()(T* ptr){cout << "delete[] ptr";delete[] ptr;}
};void TestDeletor()
{// 如果不传定制删除器,运行会报错Shared_Ptr<ListNode> sp(new ListNode[10], DeleteArr<ListNode>());
}

        到此结束,感谢大家观看♪(・ω・)ノ

相关文章:

C++智能指针详解

一、智能指针简介 智能指针是一个类似于指针的类&#xff0c;将指针交给这个类对象进行管理&#xff0c;我们就可以像使用指针一样使用这个类&#xff0c;并且它会自动释放资源。 智能指针运用了 RAII 的思想(资源获得即初始化)。RAII 是指&#xff0c;用对象的生命周期来管理资…...

基础库正则表达式

我们已经可以用requests 库来获取网页的源代码&#xff0c;得到 HTML 代码。但我们真正想要的数据是包含在 HTML代码之中的&#xff0c;要怎样才能从 HTML,代码中获取想要的信息呢?正则表达式就是其中一个有效的方法。 本篇博客我们将了解一下正则表达式的相关用法。正则表达…...

【spring专题】spring如何解析配置类和扫描包路径

文章目录 目标重要的组件加载配置类启动解析组件定位配置类解析配置类 扫描过程总结 目标 这是我们使用注解方式启动spring容器的核心代码 AnnotationConfigApplicationContext applicationContext new AnnotationConfigApplicationContext(MyConfig.class); User user (Us…...

MyBatis框架的入门

目录 MyBatis第一章&#xff1a;框架的概述1. MyBatis框架的概述 第二章&#xff1a;MyBatis的入门程序1. 创建数据库和表结构2. MyBatis的入门步骤 MyBatis 第一章&#xff1a;框架的概述 1. MyBatis框架的概述 MyBatis是一个优秀的基于Java的持久层框架&#xff0c;内部对…...

代码随想录D22-23 回溯算法01-02 Python

理论回顾 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 回溯的本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案&#xff0c;如果想让回溯法高效一些&#xff0c;可以加一些剪枝…...

【网络云计算】2024第50周-每日【2024/12/13】小测-理论-写10个Bash Shell脚本-解析

文章目录 1. 计算1到100的和2. 列出当前目录下所有文件和文件夹3. 检查文件是否存在4. 备份文件到指定目录&#xff08;简单示例&#xff09;5. 打印系统当前日期和时间6. 统计文件中的行数7. 批量重命名文件&#xff08;将.txt后缀改为.bak&#xff09;8. 查找进程并杀死&…...

MATLAB转换C语言--问题(一)FFT 和 IFFT 的缩放因子

1. MATLAB 中的 FFT 和 IFFT 在 MATLAB 中&#xff0c;fft 和 ifft 函数具有以下缩放行为&#xff1a; fft&#xff1a;执行快速傅里叶变换&#xff08;FFT&#xff09;&#xff0c;不进行缩放。ifft&#xff1a;执行逆快速傅里叶变换&#xff08;IFFT&#xff09;&#xff0c;…...

轻松上手:使用 Vercel 部署 HTML 页面教程

&#x1f600; 在学习前端的过程中&#xff0c;部署项目往往是一个令人头疼的问题。然而&#xff0c;Vercel 为我们提供了一个便捷且免费的解决方案。 Vercel 是一个强大的云平台&#xff0c;专门用于前端项目的部署和托管。它不仅支持多种前端框架和静态网站生成器&#xff0…...

如何运用 HTM?

一、HTM 概述 HTM&#xff08;Hierarchical Temporal Memory&#xff0c;分层时序记忆&#xff09;是一种基于神经科学原理构建的计算模型&#xff0c;旨在模拟大脑的学习和记忆机制&#xff0c;以处理复杂的时间序列数据和模式识别任务。它具有独特的架构和算法&#xff0c;能…...

12.16【net】【study】

路由表是路由器或者其他互联网网络设备上存储的一张表&#xff0c;它记录了到达特定网络目的地的路径。路由表中的每一行&#xff08;即一个路由条目&#xff09;包含了目的地网络地址、子网掩码、下一跳地址、出接口等信息。 Destinations&#xff08;目的地&#xff09;和 R…...

2023和2024历年美赛数学建模赛题,算法模型分析!

文末获取历年优秀论文解析&#xff0c;可交流解答 2023年题目分析 MCM&#xff08;Mathematical Contest in Modeling&#xff09; 问题 A&#xff1a;遭受旱灾的植物群落 概述&#xff1a;要求建立预测模型&#xff0c;模拟植物群落在干旱和降水充裕条件下随时间的变化。类…...

Node.js内置模块

1.内置模块 Node.js的中文网参考手册:https://nodejs.cn//api 帮助文档 API文档:查看对应的模块,左边是模块,右边是模块的成员 源码:https://github.com/nodejs/node/tree/main/lib 查看 例如: http.js 创建web服务器的模块 -->进入源码中,搜索…...

测评|携程集团25年社招在线测评北森题库、真题分析、考试攻略

携程集团社招入职测评北森题库主要考察以下几个方面&#xff1a; 1. **言语理解**&#xff1a;这部分主要测试应聘者运用语言文字进行思考和交流、迅速准确地理解和把握文段要旨的能力。 2. **资料分析**&#xff1a;包括文字题和图表题&#xff0c;考察应聘者快速找出关键信息…...

快速启动Go-Admin(Gin + Vue3 + Element UI)脚手架管理系统

Go-Admin 是一个基于 Gin Vue Element UI & Arco Design & Ant Design 的前后端分离权限管理系统脚手架。它包含了多租户支持、基础用户管理功能、JWT 鉴权、代码生成器、RBAC 资源控制、表单构建、定时任务等功能。该项目的主要编程语言是 Go 和 JavaScript。 ps&a…...

数据分流:优化数据处理流程的关键策略

引言 在大数据时代&#xff0c;企业面临着数据量的激增和数据类型的多样化。为了有效地管理和分析这些数据&#xff0c;数据分流成为了一个重要的策略。数据分流指的是将数据按照特定的规则和流程分配到不同的处理路径&#xff0c;以优化数据处理效率和准确性。本文将探讨数据…...

RabbitMQ如何构建集群?

大家好&#xff0c;我是锋哥。今天分享关于【RabbitMQ如何构建集群&#xff1f;】面试题。希望对大家有帮助&#xff1b; RabbitMQ如何构建集群&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在RabbitMQ中&#xff0c;集群&#xff08;Cluster&#x…...

RNN LSTM Seq2Seq Attention

非端到端&#xff1a; data -》 cleaning -》 feature Engining &#xff08;70%-80%工作 设计特征&#xff09;-》 分类器 -》预测 端到端 End-to-End&#xff1a; data -》 cleaning -》Deep learning&#xff08;表示学习&#xff0c;从数据中学习特征&#xff09; -》…...

硬件设计-ADC和低本底噪声为何至关重要

简介 在工程领域&#xff0c;精度是核心要素。无论是对先进电子设备执行质量和性能检测&#xff0c;还是对复杂系统进行调试&#xff0c;测量精度的高低都直接关系到项目的成功与否。这时&#xff0c;示波器中的垂直精度概念就显得尤为重要&#xff0c;它衡量的是电压与实际被…...

个性化域名配置

1 申请免费SSL证书 访问 https://certbot.eff.org &#xff0c;可申请 通配符证书&#xff0c;每次申请可以使用3个月&#xff0c;到期可以免费续期。 2 配置nginx server index.conf 配置如下&#xff1a; server {listen 80;server_name biwow.com www.biwow.com;return …...

uniapp中打包应用后,组件在微信小程序和其他平台实现不同的样式

今天&#xff0c;我们来介绍一下&#xff0c;uniapp中如何实现打包应用后&#xff0c;组件在微信小程序和其他平台不同的样式&#xff0c;在这里&#xff0c;我们使用背景颜色进行演示&#xff0c;使用 UniApp 提供的 uni.getSystemInfoSync() 方法来获取系统信息&#xff0c;包…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...