C++进阶之路---何为智能指针?

news2024/11/26 0:41:35

顾得泉:个人主页

个人专栏:《Linux操作系统》 《C++从入门到精通》  《LeedCode刷题》

键盘敲烂,年薪百万!


一、为什么需要智能指针?

       下面我们先分析一下下面这段程序有没有什么内存方面的问题?提示一下:注意分析MergeSort函数中的问题。

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	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;
}

问题分析:上面的问题分析出来我们发现有什么问题?

       程序明面上看起来没什么问题,可以正常运行,但是p1和p2没有释放,会出现内存泄漏,因为抛异常了会执行流调转。

       这时候就需要我们引入智能指针的概念了:

       智能指针是C++中的一种特殊指针,它能够自动管理动态分配的内存,避免内存泄漏和悬空指针的问题。智能指针通过在对象生命周期结束时自动释放内存,减少了手动释放内存的工作量和出错的可能性。


二、智能指针的原理

1.RAII

       RAII是Resource Acquisition Is Initialization的缩写,意为资源获取即初始化。它是一种C++编程技术,用于管理资源的生命周期。RAII的核心思想是,通过在对象的构造函数中获取资源,并在析构函数中释放资源,来确保资源的正确管理。

       具体来说,RAII的使用方法是通过在对象的构造函数中获取资源,例如打开文件、分配内存等,然后在析构函数中释放资源,例如关闭文件、释放内存等。这样,在对象的生命周期结束时,资源会自动被释放,无需手动管理。

这种做法有两大好处:

       1.不需要显式地释放资源。

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

// 使用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()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	cout << div() << endl;
}
int main()
{
	try 
	{
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

2.智能指针的原理

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

template<class T>
class SmartPtr 
{
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
private:
	T* _ptr;
};

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

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


三、智能指针的分类

1.std::auto_ptr

       C++98版本的库中就提供了auto_ptr的智能指针。

       下面演示的auto_ptr的使用及问题。其原理是:管理权转移。

namespace GoodQ
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			// 检测是否为自己给自己赋值
			if (this != &ap)
			{
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;
				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _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;
}

       结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr

2.std::unique_ptr

       C++11中开始提供更靠谱的unique_ptr.其原理是:防止拷贝。

namespace GoodQ
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
	private:
		T* _ptr;
	};
}
int main()
{
	 std::unique_ptr<int> sp1(new int);
	 return 0;
}

3.std::shard_ptr

       C++11中开始提供更靠谱的并且支持拷贝的shared_ptr。

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

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

template<class T>
class shared_ptr
{
public:
	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)
	{}
	void release()
	{
		if (--(*_pcount) == 0)
		{
			_del(_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;
	function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

std::shared_ptr的循环引用问题

struct ListNode
{
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

循环引用分析:

       1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动
delete。
       2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
       3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
       4. 也就是说_next析构了,node2就释放了。
       5. 也就是说_prev析构了,node1就释放了。
       6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

// 解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
struct ListNode
{
	int _data;
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

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

       其实shared_ptr设计了一个删除器来解决这个问题,代码同上.


四、扩展阅读

       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中的实现的。


结语:关于本次C++11中智能指针的分享到这里就结束了,希望本篇文章的分享会对大家的学习带来些许帮助,如果大家有什么问题,欢迎大家在评论区留言~~~

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

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

相关文章

通过系统防火墙,禁用同网段主机互访

要通过系统防火墙禁止同网段主机之间的互访&#xff0c;您可以在Windows操作系统中使用高级防火墙规则来实现。以下是在Windows环境中创建一条规则以阻止本地同一子网内的计算机互相访问的基本步骤&#xff1a; 对于Windows防火墙&#xff08;适用于Windows 7至Windows 11&…

一文带你全面了解功能安全软件监控方案

引言&#xff1a;功能安全标准&#xff08;ISO26262 Part6&#xff09;提到了用于错误探测的安全机制&#xff0c;其中就有程序流监控&#xff0c;如图1所示&#xff1b;本文主要探讨在AUTOSAR CP以及AP的场景下&#xff0c;怎么实现程序流监控。 图1 ISO26262 Part6 一、CP场…

Android设备使用DS file远程访问群晖NAS管理本地文件

文章目录 1. 群晖安装Cpolar2. 创建TCP公网地址3. 远程访问群晖文件4. 固定TCP公网地址5. 固定TCP地址连接 DS file 是一个由群晖公司开发的文件管理应用程序&#xff0c;主要用于浏览、访问和管理存储在群晖NAS&#xff08;网络附加存储&#xff09;中的文件。这个应用程序具有…

JS--demo实现随机点名

逻辑就是通过点击事件得到数组里面的随机一个值&#xff0c;再把这个值给删除&#xff0c;当数组长度为1的时候&#xff0c;停止点名&#xff0c;用disabled属性让用户不能进行点击。 <!DOCTYPE html> <html lang"en"><head><meta charset&quo…

基于springboot实现校园资料分享平台系统项目【项目源码+论文说明】

基于springboot实现校园资料分享平台系统演示 摘要 随着信息互联网购物的飞速发展&#xff0c;国内放开了自媒体的政策&#xff0c;一般企业都开始开发属于自己内容分发平台的网站。本文介绍了校园资料分享平台的开发全过程。通过分析企业对于校园资料分享平台的需求&#xff…

电脑微信双开,微信微信多开支持多个微信同时登录,快速切换,方便快捷 电脑最简单的微信双开多开方法 电脑上怎么登录两个微信账号?电脑微信怎么能够双开?

支持多个微信账号同时登录&#xff0c;不限微信登录个数&#xff0c;运行快速&#xff0c;稳定不卡顿 集成所有聊天窗口&#xff0c;一键快捷切换&#xff0c;窗口再多也不乱&#xff0c;提高你的工作效率 同时管理多个微信号&#xff0c;且需要分别维护用户关系、粉丝社群 …

Oracle表空间满清理方案汇总分享

目录 前言思考 一、第一种增加表空间的数据文件数量达到总容量的提升 二、第二种解决方案针对system和sysaux的操作 2.1SYSTEM表空间优化 2.2sysaux表空间回收 2.2.1针对sysaux的表空间爆满还有第二套方案维护 三、第三种解决方案使用alter tablespace resize更改表空间的…

HTML转EXE工具(HTML App Build)永久免费版:24.4.9.0

最新版本的HTML2EXE即将发布了。自从去年发布了HTML2EXE之后&#xff0c;我就正式上班了&#xff0c;一直忙于工作&#xff0c;实在没有时间更新&#xff08;上班时间不能做&#xff09;&#xff0c;很多网友下载使用&#xff0c;反应很好&#xff0c;提出了一些改进的建议&…

Tomcat项目部署spring mvc项目,压测出现的问题

Tomcat项目部署spring mvc项目&#xff0c;压测出现的问题 项目部署&#xff1a;docker部署时候设置的内存是80G&#xff0c;JVM堆内存的初始堆和最大堆设置的内存都是64G 1、压测的时候&#xff0c;并发1000&#xff0c;循环1次 日志显示&#xff1a;堆内存溢出 2、排查出…

蓝桥杯单片机之PCF8591的使用

下文仅仅讲怎么使用。 本文章参考&#xff1a; 【蓝桥杯】PCF8591 A/D D/A转换应用_pcf8591中输出电压2v怎么表示-CSDN博客 蓝桥杯电子类单片机学习三——PCF8591 AD/DA转化器&#xff0c;AT24C02 EEPROM存储器&#xff08;iic驱动&#xff09;_蓝桥杯da转换输出的是什么-CS…

dg_mmld部分复现

Ours ( K ˆ \^{K} Kˆ2)复现结果– Photo&#xff1a;0.9634730538922156 (at Epoch 23) Art&#xff1a;0.8125 (at Epoch 23) Cartoon&#xff1a;0.7713310580204779 (at Epoch 18) 差距在可接受范围内 辅助信息 If you send 作者 an e-mail, 作者 will tell you a URL w…

C/C++中const关键字用法总结

C基础专栏&#xff1a;http://t.csdnimg.cn/4FdOH 目录 1.引言 2.修饰变量 2.1.修饰普通变量 2.2.修饰指针 2.3.修饰对象 3.修饰函数形参 4.修饰函数返回值 5.修饰类成员函数 6.const与constexpr区别 7.总结 1.引言 在面试的过程&#xff0c;面试官经常都会问到const…

arm-linux-gnueabihf-gcc默认目录

默认编译的头文件目录&#xff1a; /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib 默认编译的库文件目录&#xff1a; /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/include/ …

[C++]哈希应用之位图布隆过滤器

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 前言&#xff1a; 我们之前…

Hadoop安装部署-NameNode高可用版

Hadoop分布式文件系统支持NameNode的高可用性&#xff0c;本文主要描述NameNode多节点高可用性的安装部署。 如上所示&#xff0c;Hadoop分布式文件系统部署了NameNode的Master主节点以及NameNode的Slave副节点&#xff0c;当Master主节点发生故障变得不可用时&#xff0c;ZooK…

Vulnhub:DEVCONTAINER: 1

目录 信息收集 arp nmap nikto whatweb WEB 信息收集 dirmap 文件上传 提权 系统信息收集 横向提权 信息泄露 get root 信息收集 arp ┌──(root㉿ru)-[~/kali/vulnhub] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:50:56:2f:dd…

Windows Server 2008添加Web服务器(IIS)、WebDAV服务、网络负载均衡

一、Windows Server 2008添加Web服务器&#xff08;IIS&#xff09; &#xff08;1&#xff09;添加角色&#xff0c;搭建web服务器&#xff08;IIS&#xff09; &#xff08;2&#xff09;添加网站&#xff0c;关闭默认网页&#xff0c;添加默认文档 在客户端浏览器输入服务器…

无需训练,这个新方法实现了生成图像尺寸、分辨率自由

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 近日&#xff0c;来自香港中文大学 - 商汤科技联合实验室等机构的研究者们提出了FouriScale&…

蓝桥杯-外卖店优先级

代码及其解析 #include<bits/stdc.h> using namespace std; const int N100010;int order[N]; //order[id] 第id号店上一次的订单,记录的是时间 int prior[N]; //prior[id] 第id号店的优先级 int flag[N]; //flag[id] 第id号店在不在优先缓存中struct node{int…

有关栈的算法

例题一 解法&#xff08;栈&#xff09;&#xff1a; 算法思路&#xff1a; 本题极像我们玩过的「开⼼消消乐」游戏&#xff0c;仔细观察消除过程&#xff0c;可以发现本题与我们之前做过的「括号匹配」问题是类似的。当前元素是否被消除&#xff0c;需要知道上⼀个元素的信息…