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:
		// RAII
		auto_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:
		// RAII
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			cout << "delete->" << _ptr << endl;

			delete _ptr;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
		
		// C++11
		unique_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:
		// RAII
		shared_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 = sp3
		shared_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_ptr

    return 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.mfbz.cn/a/226470.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java学习part40collections工具类

162-集合框架-Collections工具类的使用_哔哩哔哩_bilibili 1.collections工具类 感觉类似c的algorithm包&#xff0c;提供了很多集合的操作方法 2.排序 3.查找 4.复制替换 5.添加&#xff0c;同步

异常检测 | 基于孤立森林(Isolation Forest)的数据异常数据检测(结合t-SNE降维可视化)

异常检测 | MATLAB实现基于孤立森林的数据异常检测 目录 异常检测 | MATLAB实现基于孤立森林的数据异常检测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现基于孤立森林(Isolation Forest)的数据异常数据检测可视化&#xff08;完整源码和数据) 基于孤立森林(…

如何将微服务注册到nacos服务上

首先可在maven的父工程的pom文件中添加maven的dependencyManagement标签&#xff0c;引入spring-cloud-alibaba-dependencies坐标 <properties><spring.cloud.alibaba.version>2.2.9.RELEASE</spring.cloud.alibaba.version></properties><!-- 管理…

Leetcode刷题笔记题解(C++):LCR 121. 寻找目标值 - 二维数组

思路&#xff1a;从左小角或者右上角开始遍历&#xff0c;假设右上角开始遍历&#xff0c;如果当前值大于目标值则列-1&#xff1b;如果当前值小于目标值则行1&#xff0c;以此遍历来查找目标值&#xff1b;注意col和row的选取 class Solution { public:bool findTargetIn2DPl…

【Java数据结构 -- List和ArrayList与顺序表】

List和ArrayList与顺序表 一. List1.1 List介绍2.1 常见接口介绍3.1 List的使用 二. ArrayList与顺序表1.线性表2.顺序表2.1 接口的实现 3.ArrayList简介4. ArrayList使用4.1 ArrayList的构造 4.2 ArrayList常见操作4.3 ArrayList的遍历4.4 ArrayList的扩容机制5. ArrayList的具…

HarmonyOS 振动效果开发指导

Vibrator 开发概述 振动器模块服务最大化开放硬工最新马达器件能力&#xff0c;通过拓展原生马达服务实现振动与交互融合设计&#xff0c;打造细腻精致的一体化振动体验和差异化体验&#xff0c;提升用户交互效率和易用性、提升用户体验、增强品牌竞争力。 运作机制 Vibrato…

排序:快速排序(hoare版本)

目录 快速排序&#xff1a; 概念&#xff1a; 动画分析&#xff1a; 代码实现&#xff1a; 代码分析&#xff1a; 代码特性&#xff1a; 常见问题&#xff1a; 快速排序&#xff1a; 概念&#xff1a; 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法&a…

ESP32-Web-Server编程- 在 Web 上开发动态纪念册

ESP32-Web-Server编程- 在 Web 上开发动态纪念册 概述 Web 有很多有趣的玩法&#xff0c;在打开网页的同时送她一个惊喜。 需求及功能解析 本节演示在 ESP32 上部署一个 Web&#xff0c;当打开对应的网页时&#xff0c;将运行动态的网页内容&#xff0c;显示炫酷的纪念贺词…

对Spring源码的学习:二

目录 SpringBean实例化流程 Spring的后处理器 Bean工厂后处理器 SpringBean实例化流程 Spring容器在进行初始化时&#xff0c;会将xml配置的<bean>的信息封装成一个BeanDefinition对象&#xff0c;所有的BeanDefinition存储到一个名为beanDefinitionMap的Map集合中去…

2023.12.6 关于 Spring Boot 事务的基本概念

目录 事务基本概念 前置准备 Spring Boot 事务使用 编程式事务 声明式事务 Transactional 注解参数说明 Transational 对异常的处理 解决方案一 解决方案二 Transactional 的工作原理 面试题 Spring Boot 事务失效的场景有那些&#xff1f; 事务基本概念 事务指一…

基于深度学习的遥感图像变化差异可视化系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 遥感图像变化差异可视化是遥感图像处理和分析的重要研究领域之一。随着遥感技术的快速发展和遥感数据的广泛应用&#xff0c;遥感图像的获取和处理变得越来越容易…

Javaweb | Servlet编程

目录: 1.认识Servlet2.编写Servlet3.Servlet的运行机制4.Servlet的生命周期4.1 Servlet生命周期图init()方法doGet()doPost()service()destroy()方法 5.解决“控制台”打印中文乱码问题6.Servlet 和 JSP内置对象 (常用对象)获得out对象获得request 和 response对象获得session对…

KeePass开源密码管理器

KeePass开源密码管理器 KeePass 是一款免费的开源密码管理器&#xff0c;KeePass 将密码存储为一个数据库&#xff0c;而这个数据库由一个主密码或密码文件锁住&#xff0c;也就是说我们只需要记住一个主密码&#xff0c;或使用一个密码文件&#xff0c;就可以解开这个数据库&a…

每日一题:LeetCode-11.盛水最多的容器

每日一题系列&#xff08;day 13&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

服务器安装JDK17 版本显示JDK8

服务器之前安装的是JDK8&#xff0c;后面升级JDK17后&#xff0c;发现执行 java -vsrsion 显示的是此时我的环境变量已经换成了JAVA17的路径 输入&#xff1a; vim /etc/profile 解决办法&#xff1a; 1.更新自己环境变量 bash export JAVA_HOME/usr/local/jdk-17.0.7 …

【科普】什么是电子印章? PS抠的印章能用吗?

各类扣章教程一搜一大堆&#xff0c;说明大家对于电子印章使用需求很高。不过要谨记&#xff0c;不要随便抠印章用于公文、证明书、合同协议、收据发票等电子文件&#xff0c;否则可能会吃牢饭。 单是一张电子化的图片是不具备合法性的。那有的人就要问了&#xff0c;我见到的…

读书笔记-《数据结构与算法》-摘要4[插入排序]

插入排序 核心&#xff1a;通过构建有序序列&#xff0c;对于未排序序列&#xff0c;在已排序序列中从后向前扫描(对于单向链表则只能从前往后遍历)&#xff0c;找到相应位置并插入。实现上通常使用in-place排序(需用到O(1)的额外空间) 从第一个元素开始&#xff0c;该元素可…

【微服务】springboot整合quartz使用详解

目录 一、前言 二、quartz介绍 2.1 quartz概述 2.2 quartz优缺点 2.3 quartz核心概念 2.3.1 Scheduler 2.3.2 Trigger 2.3.3 Job 2.3.4 JobDetail 2.4 Quartz作业存储类型 2.5 适用场景 三、Cron表达式 3.1 Cron表达式语法 3.2 Cron表达式各元素说明 3.3 Cron表达…

封装PoiExcelUtils

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 为什么要封装PoiExcelU…

Openfire CVE-2023-32315(metasploit版)

step1&#xff1a;用docker搭建环境 Step2&#xff1a;docker查看映射端口 Step3&#xff1a;打开mysql和apache2&#xff0c;访问特定端口&#xff0c;然后靶标应用。 Step4&#xff1a;用metasploit进行攻击&#xff1a; 首先&#xff0c;打开metasploit&#xff0c;然…
最新文章