首页 > 其他分享 >智能指针

智能指针

时间:2024-05-29 09:29:47浏览次数:20  
标签:sp 智能 pcount 内存 shared ptr 指针

在谈智能指针之前,先谈谈为什么需要智能指针?

智能指针的价值

1.自动内存管理:
智能指针可以自动管理它们所指向的内存。当智能指针离开其作用域或被重置时,它们会自动删除所指向的对象,从而避免了程序员显式调用delete 的需要。这有助于减少由于忘记释放内存而导致的内存泄漏。

2.防止悬空指针:
悬空指针是指那些指向已经被释放的内存的指针。智能指针通过确保在删除所指向的对象之前不会复制或复制指针,来防止悬空指针的产生。
3.简化内存管理:
智能指针使得动态内存管理更加简单和直观。程序员可以像使用普通变量一样使用智能指针,而不需要担心内存管理的细节。
4.提高代码的可读性和可维护性:
使用智能指针可以使代码更加清晰和易于理解,因为它们封装了内存管理的细节。这有助于提高代码的可读性和可维护性。
5.与标准库和其他库兼容:
智能指针与C++标准库和其他许多库兼容,这使得它们可以在各种应用程序和项目中轻松使用。

6.安全:
通过减少程序员直接管理内存的需要,智能指针有助于提高代码的安全性。它们减少了由于内存泄漏、悬空指针和其他与内存管理相关的错误而导致的程序崩溃和数据损坏的风险。

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

C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。


系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。 

如何避免内存泄漏
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。 2. 采用 RAII 思想或者智能指针来管理资源。 3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。 4. 出问题了使用内存泄漏工具检测。 内存泄漏非常常见,解决方案分为两种: 1 、事前预防型。如智能指针等。 2 、事后查错型。如泄漏检测工具。
 智能指针的使用及原理
RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效

template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

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

		delete _ptr;
	}

private:
	T* _ptr;
};

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。

指针可以解引用,也可以通过 -> 去访问所指空间中的内容, 因此: AutoPtr  模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

即:

template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

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

		delete _ptr;
	}

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

	T* operator->()
	{
		return _ptr;
	}
private:
	T* _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;
	};

缺点:由于auto_ptr原理是管理权转移,被转移对象会悬空,因此对于不知道底层原理的人来说,可能会解引用进行访问,那么就会导致程序崩溃。

int main()
{
    std::auto_ptr<int> sp1(new int);
    std::auto_ptr<int> sp2(sp1); // 管理权转移

    // sp1悬空
    *sp2 = 10;
    cout << *sp2 << endl;
    cout << *sp1 << endl;
    return 0;
}
unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝.

std:unique_ptr:表示对对象的独占所有权。同一时间只能有一个unique_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++98的方式,如果单纯只是只声明不实现,架不住会在类外实现,因此对于C++98的方式,最好进行private私有化。

shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

std:shared_ptr:表示对对象的共享所有权。多个shared_ptr可以指向同一个对象,并且当最后一个指向该对象的shared_ptr 被销毁时,对象才会被销毁。

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

那么问题来了,这个引用计数存在在哪里呢?

是对象指向的资源里?-----不对,因为你开辟内存的时候,并不能指定在某个位置进行指定存储.

是在栈上?----不对,因为栈上变量的生命周期只在当前函数或作用域,与堆上资源生命周期不同步 

是在堆上?----对,多个智能指针可以指向同一个引用计数.

 

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

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

缺点:无法应对循环引用

循环引用

如果只有红色点这一行代码或者只有紫色这一行的代码,不会出问题;

但是当二者同时出现时,就出问题了。

原因为:

对前者而言,假设是只有红色这一行代码,那么n2先释放,但是n2的资源并不会释放,n2的资源的引用计数由2->1,然后n1释放,但是n1释放前,先释放n2的资源,然后n1才释放。

对后者而言,n2先释放,但是n2的资源不释放,n2资源的引用计数由2->1,然后释放n1,但是n1的资源不释放,n1的资源的引用计数由2->1,那么n1和n2的资源何时释放呢?

n2资源要释放-》n1的_next先释放-》n1的_next何时释放-》n1的资源释放-》n1的资源何时释放-》n2的_prev释放-》n2的_prev何时释放-》n2资源释放。

显然,我们发现,形成了一个闭环,结果表现为:资源得不到释放。

那么如何解决呢?------智能指针weak_ptr就派上用场了,weak_ptr为解决shared_ptr的循环引用问题而生。

将shared_ptr更换为weak_ptr,问题就解决了,它是怎么解决的?

_prev和_next不会增加n1和n2的引用计数。

shared_ptr的删除器

如果不是new出来的对象如何通过智能指针管理呢?

shared_ptr设计了一个删除器来解决这个问题。

	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

		template<class D>
		shared_ptr(T* ptr, D del)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _del(del)
		{}

		// function<void(T*)> _del;

		void release()
		{
			if (--(*_pcount) == 0)
			{
				//cout << "delete->" << _ptr << endl;
				//delete _ptr;
				_del(_ptr);

				delete _pcount;
			}
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		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;

		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};

通过删除器,包装器,缺省参数,这样,就解决了。

以上四种都可以得到解决。 

weak_ptr

std:weak ptr:是对 shared_ptr 的一个补充,它不会增加引用计数,因此不会导致对象被销毁。它主要用于解决shared_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;
	};

标签:sp,智能,pcount,内存,shared,ptr,指针
From: https://blog.csdn.net/wmh_1234567/article/details/139209824

相关文章

  • (算法)双指针——快乐数 <数据分块>
    1.题⽬链接:快乐数2.题⽬描述:3.题⽬分析: 为了⽅便叙述,将「对于⼀个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和」这⼀个操作记为x操作; 题⽬告诉我们,当我们不断重复x操作的时候,计算⼀定会「死循环」,死的⽅式有两种:         ▪情况⼀:⼀直在1中......
  • (算法)双指针——复写零 <原地复写>
    1.题⽬链接:1089.复写零2.题⽬描述:3.解法(原地复写-双指针): 算法思路: 如果「从前向后」进⾏原地复写操作的话,由于0的出现会复写两次,导致没有复写的数「被覆盖掉」。因此我们选择「从后往前」的复写策略。但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数......
  • AI智能体服务平台-智能客服系统-独立部署搭建
    平台简介LLM大模型是AI大脑,智能体就是AI的手和脚。我们一直在积极探索将大模型技术运用到有价值的业务场景上,而不是仅仅停留在娱乐性的聊天,探索出了以下组合使用方式:即时通讯人工客服系统+LLM大模型+RAG搜索增强知识库+RPA自动化机器人+浏览器扩展插件+语音合成TTS+Python......
  • AI程序员-人工智能编程助手
    AI程序员-人工智能编程助手在软件开发领域,人工智能编程助手正在逐步改变开发者的工作方式。这些工具利用先进的机器学习和大语言模型技术,帮助开发者提高生产效率,减少错误,并加速开发进程。本文将探讨人工智能编程助手的现状、主要工具及其带来的优势。人工智能编程助手的兴......
  • 想要初步了解指针?看这篇就够啦!
    初级指针    一:指针的解释        什么是指针?本质上指针是内存地址,平时说的指针是指针变量,用来存放地址,指针变量里面存放的是地址而通过这个地址,就可以找到一个内存单元            上示例,定义了int类型的a变量,声明了一个整型......
  • 031 指针学习—引用数组
    目录1数组元素的指针(1)定义(2)举例(3)注意事项2指针的算术运算(1)前提(2)运算规则(3)举例[1]p+i[2]p-i[3]++p与p++[4]--p与p--[5]p1-p2(4)注意事项3通过指针引用数组元素(1)引用数组元素方法(2)举例例1:输出a[10]数组中的全部元素例2:通过指针变量输出整型数组a的10个......
  • 旅行第三天【算法】双指针-----盛最多水的容器
    文章目录一、题目二、算法原理三、编写代码一、题目链接:盛最多水的容器二、算法原理首先,这种题可以用暴力解法(枚举每一种容器的大小情况),但是显然会超时(不用尝试啦,我已经试过啦!)其次还是咱们的主题----->利用双指针来求解下面先附上草稿图容器面积=高度(左......
  • 基于语音识别的智能电子病历(三)之 Soniox
    Soniox成立于2020年,目前总部位于美国加州福斯特城,该公司开发了市场上最好的语音识别引擎之一。该公司目前提供市面上领先的云转录引擎之一——这也是audioXpress成功用于采访和一般语音转文本转换的引擎。专注于语音AI的Soniox在2021年推出了世界上第一个用于语音识别的无监督......
  • Visual Studio 智能代码插件:CodeGeeX
    前言在软件开发领域,高效的编程助手一直是提升开发者效率和质量的关键。随着人工智能技术的不断发展,智能编程助手逐渐成为开发者们不可或缺的工具。其中,CodeGeeX作为一款专为VisualStudio设计的免费智能编程助手,凭借其强大的功能和便捷的使用体验,赢得了广大开发者的青睐。Co......
  • 智能指针一些实现分析
    智能指针一些实现分析提供值传递但是指针语义的功能。通过指针占用并且对管理对象,在离开作用域时释放该对象。在使用上还有另外一个很好用的功能,精简了代码复杂度,管理的对象类可以省略以下的函数默认构造函数复制构造函数复制赋值函数比如有一个类Fd用于管理fd,并且拥......