C++【智能指针】

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析(3)

在这里插入图片描述


目录

  • 👉🏻为什么需要智能指针?
  • 👉🏻 内存泄漏
    • 什么是内存泄漏,内存泄漏的危害
    • 内存泄漏分类
    • 如何避免内存泄漏
  • 👉🏻智能指针的使用及原理
    • RAII思想
    • 智能指针的原理
  • 👉🏻C++标准库提供的常见智能指针
    • auto_ptr模拟实现
    • unique_ptr模拟实现
    • shared_ptr模拟实现
      • 循环引用问题
    • weak_ptr模拟实现

👉🏻为什么需要智能指针?

没有智能指针时,开发人员需要手动管理动态内存分配和释放。这可能导致以下问题:

  1. 内存泄漏:手动管理内存很容易出现内存泄漏,即程序在不再需要使用某块动态分配的内存时未能释放它,导致系统中有大量无法访问的内存块,最终耗尽内存资源。

  2. 悬空指针:手动释放内存后,如果忘记将指针置空,就会产生悬空指针,即指向已经释放的内存的指针,当再次使用这个指针时会导致程序崩溃或者不可预期的行为。

  3. 多重释放:同一块内存被释放多次,可能会破坏内存结构,导致程序崩溃。

  4. 资源泄漏:除了内存外,还存在其他资源(如文件句柄、数据库连接等)需要手动管理,容易出现类似的泄漏和错误。

智能指针通过封装了动态分配的内存,并提供自动的内存管理机制,可以有效地解决上述问题。当智能指针超出作用域时,它所管理的资源会被自动释放,从而避免了内存泄漏和悬空指针等问题。此外,智能指针的引入也提高了代码的可读性和可维护性,减少了手动管理内存带来的麻烦,使得程序更加健壮和安全。

因此,智能指针是现代C++编程中推荐的一种重要工具,它能够简化内存管理并提高代码的安全性。

👉🏻 内存泄漏

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

  • 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
  • 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

内存泄漏分类

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

如何避免内存泄漏

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

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

👉🏻智能指针的使用及原理

RAII思想

RAII(Resource Acquisition Is Initialization)是一种C++编程中的重要设计模式,它利用对象的生命周期来管理资源的获取和释放,从而确保资源在适当的时候被正确地释放。RAII是C++语言中的一种重要特性,也是使用智能指针等资源管理类的基础理念。

RAII的核心思想可以简单概括为:在对象的构造函数中获得资源,在对象的析构函数中释放资源。通过这种方式,可以确保在任何情况下(包括异常情况),资源都能够得到正确的释放,从而避免了资源泄漏等问题

🧁RAII的优点包括

  1. 资源自动管理:通过对象的生命周期来管理资源,使得资源的获取和释放变得自动化,避免了手动管理资源带来的麻烦。
  2. 异常安全:即使在发生异常的情况下,资源也能够被正确释放,不会造成资源泄漏。
  3. 代码清晰:RAII可以提高代码的可读性和可维护性,因为资源管理的逻辑被封装在对象的构造和析构函数中,使得代码更加清晰简洁。

在实际编程中,RAII常常与智能指针文件句柄数据库连接等资源管理类一起使用,以确保资源的正确管理。同时,开发人员也可以通过自定义类来实现RAII,将资源管理逻辑封装在对象的构造和析构函数中,从而实现对任意类型资源的自动化管理。

总的来说,RAII是C++中一种强大且灵活的资源管理机制,能够帮助开发人员避免资源泄漏等问题,提高代码的稳定性和可靠性。

以下是一个采用RAII思想的例子

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if(_ptr)delete _ptr;}private:T* _ptr;
};
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{ShardPtr<int> sp1(new int);ShardPtr<int> sp2(new int);cout << div() << endl;
}
int main()
{try {Func();}catch(const exception& e){cout<<e.what()<<endl;}return 0;
}

智能指针的原理

智能指针是C++语言中用于管理动态内存的类模板,其原理基于RAII(Resource Acquisition Is Initialization)设计模式。智能指针的核心思想是利用对象的生命周期来管理动态分配的内存,以确保在适当的时机释放内存,避免内存泄漏和悬空指针等问题。

智能指针的原理可以简单概括如下:

  1. 封装指针:智能指针通过封装原始的裸指针(raw pointer),并提供对应的操作符重载和成员函数,实现对动态分配内存的访问和管理

  2. 计数引用:智能指针通常会记录当前指向的内存块被多少个智能指针共享,这就是所谓的引用计数。引用计数的增加和减少是通过智能指针的拷贝构造和析构函数来完成的。

  3. 析构函数自动释放:当一个智能指针超出其作用域时,其析构函数会自动被调用,从而触发对应的内存释放操作。如果这个智能指针是唯一指向某块内存的指针,并且没有发生深度拷贝,那么内存将被正确释放。

  4. 避免悬空指针:智能指针通常会在内部使用nullptr来避免悬空指针的问题,即确保被释放的内存块的指针在被释放后被置为空指针。

常见的智能指针包括std::unique_ptr、std::shared_ptr和std::weak_ptr。其中,std::unique_ptr用于独占拥有一个动态分配的对象,std::shared_ptr用于多个指针共享拥有一个对象,而std::weak_ptr则用于协助std::shared_ptr进行循环引用的解决。

🥙 总结一下智能指针的原理:

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

👉🏻C++标准库提供的常见智能指针

C++中的智能指针是一种用于管理动态分配的内存的工具,它可以帮助开发人员避免内存泄漏和悬空指针等问题。C++标准库提供了三种主要的智能指针类型:std::unique_ptrstd::shared_ptrstd::weak_ptr

  1. std::unique_ptr

    • std::unique_ptr代表独占所有权的指针,即同一时刻只能有一个std::unique_ptr指向特定的资源(如堆上的对象)。
    • std::unique_ptr被销毁时,它所管理的资源也会被自动释放,从而避免了内存泄漏的风险。
    • std::unique_ptr不支持拷贝构造和赋值操作,因此确保了资源的独占性。
  2. std::shared_ptr

    • std::shared_ptr允许多个指针共享对同一资源的所有权,它使用引用计数来跟踪资源的引用次数。
    • 当最后一个指向资源的std::shared_ptr被销毁时,资源会被释放。
    • std::shared_ptr的引用计数机制可以防止资源过早释放,但也可能导致循环引用的问题。
  3. std::weak_ptr

    • std::weak_ptr是为了解决std::shared_ptr可能出现的循环引用问题而引入的。
    • 它允许观察由std::shared_ptr管理的资源,但不拥有资源。因此,使用std::weak_ptr可以打破std::shared_ptr可能出现的循环引用,避免内存泄漏。

4.auto_ptr是C++98标准中提供的一种简单的智能指针,用于管理动态分配的对象。与std::unique_ptr不同,auto_ptr允许多个指针拥有同一个对象,并且支持从一个auto_ptr向另一个auto_ptr的所有权转移(move semantics)。

auto_ptr的使用方法类似于裸指针,可以通过*操作符来获取对象的引用,也可以使用->操作符来调用成员函数。当auto_ptr超出其作用域时,其析构函数会自动释放内存,避免了内存泄漏的问题。

auto_ptr的坏处主要包括以下几点

  1. 不支持数组:auto_ptr只能用于管理单个对象,而不能用于管理数组。因为auto_ptr采用delete来释放对象,而不是delete[],这可能导致释放整个数组的行为未定义。

  2. 存在所有权转移的问题:auto_ptr支持从一个auto_ptr向另一个auto_ptr的所有权转移,这可能会导致潜在的问题。比如,如果两个auto_ptr同时指向同一个对象,那么当其中一个auto_ptr释放了该对象时,另一个auto_ptr就变成了一个悬空指针,这可能会带来严重的后果。

  3. 已经被废弃:auto_ptr已经被C++11标准废弃,原因是auto_ptr的设计存在安全问题,并且容易导致程序出现未定义的行为。C++11标准引入了std::unique_ptr和std::shared_ptr来取代auto_ptr,提供更加安全和灵活的内存管理方式。
    综上所述,auto_ptr虽然是一种简单的智能指针,但存在很多的坏处。由于auto_ptr已经被废弃,开发人员应该尽量避免使用它,并选用更加安全和灵活的内存管理方式。

auto_ptr模拟实现

template<class T>class auto_ptr{public:// RAIIauto_ptr(T* ptr):_ptr(ptr){}~auto_ptr(){if (_ptr){cout << "delete->" << _ptr << endl;delete _ptr;_ptr = nullptr;}}// ap2(ap1)auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};

unique_ptr模拟实现

template<class T>class unique_ptr{public:// RAIIunique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){cout << "delete->" << _ptr << endl;delete _ptr;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// C++11unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;private:// C++98// 1、只声明不实现// 2、限定为私有//unique_ptr(const unique_ptr<T>& up);//unique_ptr<T>& operator=(const unique_ptr<T>& up);private:T* _ptr;};

在C++中,当在类中声明了拷贝构造函数,并将其标记为"=delete"时,表示禁用了该拷贝构造函数。这样的语法用于阻止编译器生成默认的拷贝构造函数,同时也禁止使用拷贝构造函数进行对象的拷贝。

shared_ptr模拟实现

template<class T>class shared_ptr{public:// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}void release(){if (--(*_pcount) == 0){//cout << "delete->" << _ptr << endl;delete _ptr;delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount);}// sp1 = sp3shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int use_count() const{return *_pcount;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;};

循环引用问题

当使用shared_ptr时,循环引用可能会发生在两个或多个对象之间,它们相互持有对方的shared_ptr。以下是一个简单的代码示例,演示了循环引用的情况:

#include <memory>class A;
class B;class A {
public:std::shared_ptr<B> bPtr;~A() {std::cout << "A destructor" << std::endl;}
};class B {
public:std::shared_ptr<A> aPtr;~B() {std::cout << "B destructor" << std::endl;}
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->bPtr = b;b->aPtr = a;return 0;
}

在上面的代码中,类A和类B分别拥有对方的shared_ptr。main函数中创建了一个A对象和一个B对象,并将它们的shared_ptr互相赋值,形成了循环引用。

由于循环引用的存在,当这段代码执行完毕后,A对象和B对象的引用计数都不会降为零,导致它们的析构函数不会被调用,内存泄漏。

要解决这个循环引用问题,可以使用weak_ptr来打破循环引用,如下所示:

#include <memory>class A;
class B;class A {
public:std::shared_ptr<B> bPtr;~A() {std::cout << "A destructor" << std::endl;}
};class B {
public:std::weak_ptr<A> aPtr;~B() {std::cout << "B destructor" << std::endl;}
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->bPtr = b;b->aPtr = a;  // 使用weak_ptrreturn 0;
}

在这个示例中,类B的aPtr成员变量被改为了std::weak_ptr,它不会增加引用计数。通过使用weak_ptr打破了循环引用,使得A对象和B对象可以正确地释放内存。

请注意,为了简化示例,上述代码没有完全展示资源管理的完整过程,只着重展示了循环引用问题以及使用weak_ptr解决循环引用的方法。在实际编程中,需要根据具体情况综合考虑使用智能指针、弱引用等技术来管理内存和资源。

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;};

如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

实验3.5 路由器的单臂路由配置

实验3.5 路由器的单臂路由配置 一、任务描述二、任务分析三、具体要求四、实验拓扑五、任务实施1.SWA的基本配置2.RA的基本配置3.在RA上查看接口状态 六、任务验收七、任务小结 一、任务描述 某公司对部门划分了需VLAN之后&#xff0c;发现两个部门之间无法通信&#xff0c;但…

机器学习——logistic回归

目录 一、线性模型与回归 二、基于logistic回归和Sigmoid函数的分类 三、最优化算法 1. 最大似然估计 2. 梯度上升法 3. 训练算法&#xff1a;梯度上升 4. 绘制决策边界 5. 训练算法&#xff1a;随机梯度上升 6. 改进的随机梯度算法 四、从疝气病症预测病马的死亡率 …

在jupyter notebook中修改其他文件的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

力扣题:数字与字符串间转换-12.8

力扣题-12.8 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;299. 猜数字游戏 解题思想&#xff1a;进行遍历&#xff0c;统计完全相同的数字和不相同的数字即可&#xff0c;然后统计不相同的数字在秘密数字和猜测数字中共同出现的次数 class Sol…

Kubernetes(K8s 1.27.x) 快速上手+实践,无废话纯享版(视频笔记)

视频源&#xff1a;1.03-k8s是什么&#xff1f;_哔哩哔哩_bilibili 1 基础知识 1.1 K8s 有用么&#xff1f; K8s有没有用 K8s要不要学&#xff1f; 参考资料: https://www.infoq.com/articles/devops-and-cloud-trends-2022/?itm_sourcearticles_about_InfoQ-trends-report…

SSL证书代理

众所周知&#xff0c;SSL证书已经成为当下网络安全中不可或缺的一个环节&#xff0c;对于很多开发公司来说&#xff0c;给自己的客户提供SSL证书安全服务也是最为基础的。 但是目前市面上像阿里云之类的证书服务商对于开发公司需要的证书并没有太大的一个优惠政策&#xff0c;给…

MySQL老是卸载不干净,不会删除注册表,安装总是报错

给大家推荐一款非常使用的工具 geek点击官网下载。 安装完成主页就长这样&#xff1a; 右键点击你要删除的MySQL卸载即可。自动帮你清空注册表等信息。 谁用谁知道&#xff01;&#xff01;&#xff01; 用了感觉不错的话记得回来给我点赞加评论哦&#xff01;&#xff01;&…

JVM 运行时参数

面试题 JVM的参数&#xff0c;你知道的说一下 (百度) 说说你知道的几种主要的JVM参数&#xff08;京东&#xff09; JVM调优调的哪些参数&#xff1f;在哪里写这些参数&#xff1f; &#xff08;亚信&#xff09; 内存调优参数都有什么&#xff1f;&am…

MTU TCP-MSS(转载)

MTU MTU 最大传输单元&#xff08;Maximum Transmission Unit&#xff0c;MTU&#xff09;用来通知对方所能接受数据服务单元的最大尺寸&#xff0c;说明发送方能够接受的有效载荷大小。 是包或帧的最大长度&#xff0c;一般以字节记。如果MTU过大&#xff0c;在碰到路由器时…

介绍java spring 提供的默认数据库持久化技术 JdbcTemplate基本演示

之前 我们说过spring贴心的内嵌了三种数据源形式 其中默认为HikariCP 其实 spring 也提供了持久化数据库连接技术 这个技术其实大部分都接触过 那就是 JDBC 随着时代的发展 用他的人也越来越少了 那么 我们要演示 JdbcTemplate 导入 mybatis 或 mybatis-plus 的片段 就要注掉了…

【从零开始学习JVM | 第五篇】快速了解运行时数据区

前言&#xff1a; 当谈论 Java 程序的运行机制时&#xff0c;JVM&#xff08;Java 虚拟机&#xff09;的运行时数据区是一个必不可少的话题。JVM 运行时数据区是 Java 程序在运行过程中分配内存和管理数据的重要区域&#xff0c;它包括了方法区、堆、虚拟机栈、程序计数器和本地…

Linux---日志管理

本章主要介绍Linux中的日志管理 了解rsyslog是如何管理日志的查看日志的方法 日志管理简介 工作当中的日志&#xff0c;特指硬件和软件的日志&#xff0c;管理员可以通过它来检查错误发生的原因&#xff0c;或者寻找受到攻击时攻击者留下的痕迹。日志管理包括管理系统日志、应…

智能外呼常见场景有哪些?

智能外呼常见场景是什么&#xff1f; 智能外呼在各种场景下都有应用&#xff0c;以下是一些常见的场景&#xff1a; 营销推广 通过智能外呼向潜在客户进行产品或服务的宣传和推广&#xff0c;收集客户对产品或服务的反馈。根据客户的反馈自动调整宣传策略&#xff0c;从而提…

mac本地部署stable-diffusion

下载Homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" ①输入“1”选择中科大版本&#xff0c;然后输入Y(YES)&#xff0c;直接输入开机密码&#xff08;不显示&#xff09;然后回车确认&#xff0c;开始下载 ②…

小航助学2023年6月GESP_Scratch二级真题(含题库答题软件账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号 单选题3.00分 删除编辑附件图文 答案:D 第1题高级语言编写的程序需要经过以下&#xff08; &#xff09;操作&#xff0c;可以生成在计算机上运行的可执行代码。 A、编辑B、…

​LeetCode解法汇总1466. 重新规划路线

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 描述&#xff1a; n 座城市&…

在王者荣耀中脸探草丛的正确姿势是什么?

引言 Cocos中躲草丛效果的实现原理。 在游戏开发中&#xff0c;我们经常用透视或者半透明效果去表现模型被遮挡的效果。 本文将介绍一下如何在Cocos中实现王者荣耀中的躲草丛效果。 本文源工程在文末获取&#xff0c;小伙伴们自行前往。 躲草丛效果的实现原理 要在Cocos中…

Android Studio的笔记--String和byte[]

String和byte[]的相互转换&#xff0c;字节数组转换 String转换byte[]文本16进制字节数组 byte[]转换String文本16进制 其它 String转换byte[] 文本 将字符串&#xff08;String&#xff09;转换为字节&#xff08;byte&#xff09;的方法。默认使用的是UTF-8编码 StandardCh…

Linux本地部署1Panel服务器运维管理面板并实现公网访问

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

linux进程通信

匿名管道 struct_file的两套资源 管道只能单向通信 特征 1.如果管道没有数据 读端在读 默认会直接阻塞正在读取的进程 2.写端写满 在写会阻塞 等待对方读取 管道设计 命名管道 实现管道通信 #pragma once #include<iostream> #include<string> #include<sys/…