智能指针:作用 | 使用 | 原理 | 内存泄漏

news2024/9/22 19:10:23

在这里插入图片描述

🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git

🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈
本科在读菜鸡一枚,指出问题及时改正

文章目录

  • 智能指针的作用
  • 智能指针的使用与原理
    • RAII
    • 智能指针的基本框架
    • std::auto_ptr
    • std::unique_ptr
    • std::shared_ptr
      • 使用
      • 模拟实现
      • 线程安全问题
      • 循环引用
    • std::weak_ptr
    • 定制删除器
  • 内存泄漏问题
  • C++11和boost中智能指针的关系

智能指针的作用

由于异常的存在,反复横跳可能会导致内存泄漏,不同的异常处理逻辑没有妥善管理内存分配和释放,可能会在某些路径中遗漏delete 操作,从而导致内存泄漏。

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}

int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

如果 p1new int 抛出异常:这通常是因为内存分配失败。在这种情况下,程序会终止到这一行,控制权会转移到 main 函数中的 catch 块,因为 Func 中没有处理这个异常。

如果 p2new int 抛出异常:同样的道理,如果 new intp2 的分配中抛出异常,它也会传播到 main,并在 catch 块中处理。

如果 div 抛出异常(例如除以零):同样,这个异常会被 main 中的 catch 块捕获,输出 “除0错误”。

在所有情况下,如果在 delete p1delete p2 之前抛出了异常,程序将不会执行到这两行。因此,内存泄漏的风险在这种情况下是存在的,因为如果 new 语句抛出异常,就不会有对应的 delete 调用。

智能指针的使用与原理

RAII

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

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

这种做法有两大好处:

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

使用RAII思想设计一个Smartptr类:

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

	~SmartPtr()
	{
		delete[] _ptr;
		cout << "delete: " << _ptr << endl;
	}

private:
	T* _ptr;
};

double Division(int a, int b)
{
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void Func()
{
	SmartPtr<int> sp1(new int[10]);
	SmartPtr<int> sp2(new int[20]);

	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}

	return 0;
}

智能指针的基本框架

智能指针也是一个指针,因此也需要运算符重载

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

	~SmartPtr()
	{
		delete[] _ptr;
		cout << "delete: " << _ptr << endl;
	}

	T* get()
	{
		return _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return *_ptr;
	}

	T& operator[](size_t i)
		{
		return _ptr[i];
	}


private:
	T* _ptr;
};

std::auto_ptr

上述基本框架解决了指针问题,但是没有解决拷贝的问题。
智能指针有自己的发展历史,拷贝的思想是不一样的。

std::auto_ptr文档
C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。
auto_ptr的实现原理:管理权转移的思想

在这里插入图片描述

特点:拷贝构造时转移管理权,sp1是一个将亡值,进行资源转移,sp1是一个左值,资源转移导致sp1对象悬空,因此无法再安全访问 sp1。任何尝试解引用或访问sp1都会导致未定义行为。
不推荐使用这一做法

内部核心:

auto_ptr(auto_ptr<T>& sp)
	:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}

std::unique_ptr

unique_ptr阅读文档
在这里插入图片描述

在这里插入图片描述

std::unique_ptr使用:

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
//private:
	int _a1 = 1;
	int _a2 = 1;
};

int main()
{
	unique_ptr<A> sp1(new A);
	//获取原生指针
	A* p = sp1.get();
	cout << p << endl;
	//访问元素
	sp1->_a1++;
	sp1->_a2++;

	return 0;
}

特点:std::unique_ptr不支持拷贝,建议使用。

std::shared_ptr

使用

std::shared_ptr文档

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

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

std::shared_ptr的使用:

int main()
{
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2(sp1);

	return 0;
}

在这里插入图片描述

在这里插入图片描述

模拟实现

在模拟实现shared_ptr中,引用计数器需要使用指针来实现;
在这里插入图片描述

赋值的原理:
在执行赋值的操作时,需要先对sp1pcount进行--操作,因为此时的pcount==2,如果直接修改sp1pcount,导致数据不匹配,因此需要先将pcount的值减为1,再去赋值,这样才能保证引用计数器的值是正确的。这样可以避免内存泄漏。
在这里插入图片描述

自己给自己赋值原理:只要资源一样就是自己给自己赋值,直接返回对应指针就行。

完整代码:

#pragma once

namespace gwj
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

		//sp2(sp1)
		shared_ptr(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)
			{
				this->release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				++(*_pcount);
			}

			return *this;
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				//最后一个管理的对象释放资源
				delete _ptr;
				delete _pcount;
			}
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				//最后一个管理的对象释放资源
				delete _ptr;
				delete _pcount;
			}
		}

		int use_count()
		{
			return *_pcount;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return *_ptr;
		}

	private:
		T* _ptr;
		int* _pcount;  //引用计数器,用指针实现
	};
}

线程安全问题

智能指针对象本身拷贝析构是线程安全的,底层引用计数加减是安全的,指向的资源访问不是线程安全的(该加锁需要加锁)

智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++--,这个操作不是原子的,引用计数原来是1++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++--是需要加锁的,也就是说引用计数的操作是线程安全的。

循环引用

在这里插入图片描述

struct Node
{
	shared_ptr<Node> _next;
	shared_ptr<Node> _prev;
	int _val;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

int main()
{
	shared_ptr<Node> p1(new Node);
	shared_ptr<Node> p2(new Node);

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	p1->_next = p2;
	p2->_prev = p1;

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	return 0;
}

在这里插入图片描述

std::weak_ptr

在这里插入图片描述

不支持RAII,不单独管理资源,辅助解决shared_ptr的循环引用。本质是赋值或拷贝时,只指向资源,但是不增加shared_ptr的引用计数。

struct Node
{
	//shared_ptr<Node> _next;
	//shared_ptr<Node> _prev;
	weak_ptr<Node> _next;
	weak_ptr<Node> _prev;
	int _val;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

int main()
{
	shared_ptr<Node> p1(new Node);
	shared_ptr<Node> p2(new Node);

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	p1->_next = p2;
	p2->_prev = p1;

	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;

	return 0;
}

在这里插入图片描述

定制删除器

定制删除器可以通过在 std::shared_ptr 中使用自定义的删除函数来管理资源。

class A
{
public:
	A()
	{}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a1 = 1;
	int _a2 = 1;
};

// 定制删除器
template<class T>
struct FreeFunc {
	void operator()(T* ptr)
	{
		cout << "free:" << ptr << endl;
		free(ptr);
	}
};

int main()
{
	//std::shared_ptr<A[]> sp1(new A[10]);
	std::shared_ptr<A[]> sp1(new A[2], [](A* ptr) {delete[] ptr; });

	std::shared_ptr<int> sp2((int*)malloc(4), FreeFunc<int>());

	std::shared_ptr<FILE> sp3(fopen("test.txt", "w"), [](FILE* ptr) {fclose(ptr); });

	return 0;
}

在这里插入图片描述

内存泄漏问题

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

C++11和boost中智能指针的关系

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

golang学习笔记4-基本数据类型

注&#xff1a;本人已有C&#xff0c;C,Python基础&#xff0c;只写本人认为的重点。 go的数据类型如下 由于bool和c类似&#xff0c;和go的区别是&#xff0c;bool的值只能取true和false&#xff0c;不能取整数&#xff0c;而且有默认值false。 一、整数型 整数型存放整数&…

设计模式之策略模式例题

答案&#xff1a;A 知识点&#xff1a; 策略模式又叫模板方法模式 它的意图是定义一个操作中的算法骨架。而将一些步骤延迟到子类中&#xff0c;使得子类可以不改变一个算法的结构即可重新定义算法的某些特定步骤

3.5.2 __ipipe_init()之完成中断处理程序设置

点击查看系列文章 》 Interrupt Pipeline系列文章大纲-CSDN博客 原创不易&#xff0c;需要大家多多鼓励&#xff01;您的关注、点赞、收藏就是我的创作动力&#xff01; 3.5.2 __ipipe_init()之完成中断处理程序设置 __ipipe_init()最核心的就是__ipipe_enable_pipeline()&am…

分享两道算法题

分享两道算法题 王者荣耀分组 题目描述 部门准备举办一场王者荣耀表演赛&#xff0c;有 10 名游戏爱好者参与&#xff0c;分 5 为两队&#xff0c;每队 5 人。 每位参与者都有一个评分&#xff0c;代表着他的游戏水平。 为了表演赛尽可能精彩&#xff0c;我们需要把 10 名参赛…

十七,Spring Boot 整合 MyBatis 的详细步骤(两种方式)

十七&#xff0c;Spring Boot 整合 MyBatis 的详细步骤(两种方式) 文章目录 十七&#xff0c;Spring Boot 整合 MyBatis 的详细步骤(两种方式)1. Spring Boot 配置 MyBatis 的详细步骤2. 最后&#xff1a; MyBatis 的官方文档&#xff1a;https://mybatis.p2hp.com/ 关于 MyBa…

内网渗透-红日1

红日靶场1 渗透测试过程外网打点突破边界内网横向权限维持最后 渗透测试过程 本文章只说明渗透测试思路和技巧&#xff0c;对域靶场搭建不进行赘述 web-ip外网设置为 192.168.119.130&#xff0c;kali和外网ip同网段 外网打点 kali扫描目标ip nmap扫描目标网段   nmap -P…

【记录】大模型|Windows 下 Hugging Face 上的模型的通用极简调用方式之一

这篇文是参考了这篇&#xff0c;然后后来自己试着搭了一下&#xff0c;记录的全部过程&#xff1a;【翻译】Ollama&#xff5c;如何在 Ollama 中运行 Hugging Face 中的模型_ollama 导入 huggingface-CSDN 博客 另外还参考了这篇&#xff1a;无所不谈,百无禁忌,Win11 本地部署无…

【C++初阶】探索STL之——vector

【C初阶】探索STL之——vector 1.什么是vector2.vector的使用2.1 vector的定义2.2 vector iterator(迭代器)的使用2.3 vector空间问题2.4 vector的增删查改2.5 vector迭代器失效的问题2.5.1 vector常见迭代器失效的操作 3 动态二位数组 1.什么是vector vector其实就是一个可以…

iPhone16,超先进摄像头系统?丝滑的相机控制

iPhone 16将于9月20号正式开售&#xff0c;这篇文章我们来看下iPhone 16 在影像方面&#xff0c;有哪些升级和新feature。 芯片&#xff1a;采用第二代 3纳米芯片&#xff0c;A18。 摄像头配置&#xff1a; iPhone 16 前置&#xff1a;索尼 IMX714 &#xff0c;1200 万像素&am…

SQL 多表联查

目录 1. 内联接&#xff08;INNER JOIN&#xff09; 2. 左外联接&#xff08;LEFT JOIN&#xff09; 3. 右外联接&#xff08;RIGHT JOIN&#xff09; 4. 全外联接&#xff08;FULL JOIN&#xff09; 5. 交叉联接&#xff08;CROSS JOIN&#xff09; 6. 自联接&#xff0…

简单题101. 对称二叉树 (python)20240922

问题描述&#xff1a; python: # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right rightclass Solution(object):def isSymm…

网络通信——OSI七层模型和TCP/IP模型

OSI模型 一.OSI七层模型 OSI&#xff08;Open System Interconnect&#xff09;七层模型是一种将计算机网络通信协议划分为七个不同层次的标准化框架。每一层都负责不同的功能&#xff0c;从物理连接到应用程序的处理。这种模型有助于不同的系统之间进行通信时&#xff0c;更…

pycharm连接远程linux服务器上的docker进行深度学习训练

实习过程中由于GPU都在服务器上&#xff0c;编辑代码很麻烦。并且服务器上配置了docker的环境&#xff0c;所以用pycharm连接远程服务器的docker进行深度学习&#xff0c;这样在本地调用远程服务器的GPU和环境&#xff0c;更方便一点&#xff0c;将这个过程记录下来&#xff0c…

如何将MySQL卸载干净(win11)

相信点进来的你肯定是遇到了这个问题&#xff0c;那就是在安装MySQL的时候操作错误&#xff0c;最后结果不是自己想要的。卸载重新安装又发现安装不了。其实最主要的原因就是没有将MySQL卸载干净&#xff0c;那么如何把MySQL卸载干净&#xff1f;下面本篇文章就来给大家一步步介…

【C++】二叉搜索树的底层以及实现

个人主页 文章目录 ⭐一、二叉搜索树的概念&#x1f680;二、二叉搜索树性能分析&#x1f3dd;️三、二叉搜索树的操作1. 插入2. 查找3. 删除4. 遍历节点 &#x1f384;四、二叉搜索树的实现&#xff08;K模型&#xff09;&#x1f389;五、二叉搜索树的应用1. K模型2. KV模型…

14. PEFT:在大模型中快速应用 LoRA

如果你对LoRA还没有一个直观的概念&#xff0c;可以回看这篇文章&#xff1a;《3. 认识 LoRA&#xff1a;从线性层到注意力机制》。 我们将在这里进一步探讨如何快速地在大型预训练模型中应用 LoRA&#xff0c;并解答可能存在的问题&#xff0c;包括&#xff1a; peft 和 lora …

NSSCTF刷题篇1

js类型 [SWPUCTF 2022 新生赛]js_sign 这是一道js信息泄露的题目直接查看源码&#xff0c;有一个main.js文件点击之后&#xff0c;有一串数字和一段base64编码&#xff0c;解开base64编码得到这个编码为敲击码 解码在线网站&#xff1a;Tap Code - 许愿星 (wishingstarmoye.…

828华为云征文|华为云Flexus云服务器X实例之openEuler系统下部署k8s管理面板KubePi

828华为云征文&#xff5c;华为云Flexus云服务器X实例之openEuler系统下部署k8s管理面板kubepi 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、 KubePi介绍2.1 KubePi简介2.2 KubePi主要特点&am…

序列化方式二——JSON之Gson

Gson 1、什么是Gson? Gson是Google提供的一个用于Java编程语言的JSON&#xff08;JavaScript Object Notation&#xff09;序列化和反序列化库。它允许开发者在Java对象和JSON数据之间进行高效的映射和转换。 官网地址&#xff1a;https://github.com/google/gson 官网文档…

小程序隐私合规自查指南

一 背景&#xff1a;小程序作为一种轻量级应用&#xff0c;广泛应用于各大互联网平台。工信部通报2022年第5批侵害用户权益名单中首次出现8款违规小程序。各监管单位对“小程序”违规收集个人信息监控手段和监控力度不断加强。 工信部APP违法违规通报 上海市委网信办查处违规小…