前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++】智能指针

【C++】智能指针

作者头像
lovevivi
发布2023-10-17 09:06:26
1410
发布2023-10-17 09:06:26
举报
文章被收录于专栏:萌新的日常

1. 为什么需要智能指针?

若p1处new抛异常,则相当于p1的new没有成功,则什么都不用做


若p2处new抛异常,则相当于p2的new没有成功,而p1的new成功了,所以需要释放p1,然后再重新抛出


若div处抛异常,则将p1与p2都释放,再将其重新抛出


可以看出处理起来非常麻烦,存在内存泄漏的问题(只进行new,但没有delete) 第二个new抛异常要释放第一个new,div抛异常要释放前两个new 若再添加一个new,则又会存在new抛异常的问题,还需添加 try catch

为了提前预防内存泄漏的问题,就提出了智能指针

2. 智能指针的使用

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。 在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处: 不需要显式地释放资源 对象所需的资源在其生命期内始终保持有效

RAII是一种思想,智能指针是这种思想的产物

智能指针的常见问题

1.使用对象的生命周期去控制资源

创建一个私有的成员变量 _ptr指针 在构造函数时,将指针保存起来 在析构函数时,将指针释放

将申请的资源,交给智能指针对象去管理 (通过这个指针 去构造一个智能指针对象,这个对象会把指针保留起来)


创建对象时,会调用构造函数,将new int 传给类中的指针,对象会把指针保留起来 v1和v2属于局部对象,出了作用域时,就会调用析构函数 ,完成释放

若第一个new抛异常,就不会进入构造函数中 若第二个new抛异常,则调用析构,将第一个new释放掉 若div抛异常,则v1和v2对象都调用析构,将第一个new和第二个new都释放掉

通过类的构造和析构的自动调用,利用对象的生命周期来管理资源,被称之为 RAII

2. 像指针一样使用

在类中实现 operator() 和operator->,使对象可以进行解引用 和->访问成员的操作


3. 拷贝问题

因为没有在类中实现拷贝构造,默认是浅拷贝 ,所以就会导致释放两次,从而报错

深拷贝是不可以的,因为指针拷贝要的就是浅拷贝

链表等迭代器 结构与智能指针类似,用的是浅拷贝,为什么没有问题? 因为迭代器不管资源的释放,资源释放是容器处理的 智能指针需要管资源释放,所以不能单纯的浅拷贝

auto_ptr ——管理权转移

当上述v1和v2都管理这个资源就会有问题,两者都会去释放,导致释放两次


所以C++98版本的库中就提供了auto_ptr的智能指针,提出了 管理权转移的思想

官方文档:auto_ptr


将管理权只给一个对象,剩下一个对象去除管理权


如果不了解管理权转移的特性,就不知道v1已经为空,依旧对v1进行解引用,就会报错 因为管理权转移后,v1悬空不能访问

所以管理权转移,存在被拷贝对象悬空的问题

unique_ptr ——防拷贝

官方文档:unique_ptr


在C++98和C++11之间 产生了一个 库 boost (准标准库) 在boost中 就把智能指针的问题解决了 boost 中包含 scoped_ptr shared_ptr weak_ptr 体系

C++11将其吸收过来以后,将 scoped_ptr 改成 unique_ptr ,其他没变 即 unique_ptr shared_ptr weak_ptr unique_ptr的特点为 简单 粗暴 防拷贝 (不需要拷贝的场景)


C++98版本

拷贝构造和赋值是默认成员函数,若自己不实现,会自动生成,所以必须写 但是写又不知道写什么,所以C++98思路是只声明,不实现

只在 类里面声明是不可以的,因为在类外可以实现 所以还要声明成私有

在这里插入图片描述
在这里插入图片描述
C++11版本

使用禁止生成默认函数的关键字 delete

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

不受公有 或者 私有的 影响

shared_ptr (根本解决拷贝问题)

官方文档:shared_ptr


特点为使用引用计数,支持拷贝

有两个对象指向资源,当析构时,会析构两次 为了解决这个问题,就增加一个引用计数,若只有一个对象,就为1,若为两个对象,则为2


当其中一个对象要析构时,就先看引用计数,若引用计数减1还大于0,就要什么都不管 若引用计数减1为0,则表示最后一个管理这块资源的对象,就可以将其释放掉


若将引用计数设置为静态 (静态的成员 是属于这个类的所有对象)


对象C指向与 对象A /B不同的资源,当对 C进行释放时,也会影响到对A和B的引用计数的值 所以不能使用 静态的引用计数


每个资源都应该配对一个引用计数


new一个引用计数 对象除了指向资源,也要指向引用计数


在构造时,先new一块空间,让_pcount指向这块空间


只有当引用计数 为0时,才会去析构 释放


拷贝构造是浅拷贝,通过引用计数的方式,每次有一个对象指向资源,就使引用计数+1

赋值

情况1

若将对象C赋值给对象A,则使对象A指向对象C的资源处 ,同时对象B的引用计数-1,对象C的引用计数+1


情况2

若将对象A赋值给对象C,则对象C原本指向的引用计数为0,该资源要被释放

代码实现
代码语言:javascript
复制
namespace yzq
{
	template<class T>
	class shared_ptr
	{
	public:
		//构造
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

		//析构
		~shared_ptr()
		{
			Release();
		}
		void Release()
		{
			//若引用计数-- 后为0,则说明为最后一个对象 就进行释放
			if (--(*_pcount) == 0)
			{
				cout << "delete: " << _ptr << endl;
				//释放资源和引用计数
				delete _ptr;
				delete _pcount;
			}
		}

		void Addcount()
		{
			++(*_pcount);

		}
         
		//拷贝构造(浅拷贝)
		shared_ptr(const shared_ptr<T>&sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			//拷贝后将计数++
			Addcount();
		}


		//赋值
		 shared_ptr<T>& operator=(const shared_ptr<T>&sp)
		{
			 //若指向同一块资源 就不需要赋值
			 if (_ptr != sp._ptr)
			 {
				 //被赋值的对象 引用计数--
				 if (--(_pcount) == 0)
				 {
					 //若为0,则释放资源和引用计数
					 delete _ptr;
					 delete _pcount;
				 }
				 //改变指向
				 _ptr = sp._ptr;
				 _pcount = sp._pcount;

				 //改变指向后的对象 的引用计数++
				 (*_pcount)++;
			 }
			return *this;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T* get()//获取指针
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
	};

	void test_shared()
	{
		shared_ptr<int>v1(new int(1));
		shared_ptr<int>v2(v1);
	}


} 
weak_ptr —— 循环引用

new出两个节点,将两个节点链接起来,再删除节点

但是n1与n2链接时,可能会抛异常,会没有释放 所以将其改为使用智能指针


使用智能指针就进行释放了


n1和n2作为智能指针对象,而next和prev作为原生指针 智能指针对象是没办法给原生指针的


将next和prev都转化为智能指针即可 解决问题 但是节点不释放了

节点使用原生指针可以释放,而使用智能指针不能释放,这样的问题被称为循环引用


n1与n2都是智能指针,分别去管理资源

n2对应的引用计数为1,将n1的_next指向n2,导致n2的引用计数加1


n1的引用计数为1,将n2的_prev指向n1,导致n1的引用计数加1


出了作用域,先将n2节点析构,使其引用计数减1,此时n2的引用计数为1,还有一个_next的智能指针指向n2,只有当_next析构n2才能析构,而_next是随着n1节点析构而析构 再将n1节点析构,使其引用计数减1,此时n1的引用计数为1,还有一个_prev的智能指针指向n1,只有当_prev析构n1才能析构,而_prev是随着n2节点析构而析构

就造成了循环引用,从而导致内存泄漏


库中为了解决循环引用的问题,所以提出了 weak_ptr(弱指针)

特点: 不是常规的智能指针,不支持RAII(利用对象生命周期来控制程序资源) 支持像指针一样 专门设计出来辅助解决 shared_ptr的循环引用问题


将_next和_prev改为 weak_ptr即可解决问题 使用weak_ptr可以指向资源,但不参与管理,不增加引用计数


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 为什么需要智能指针?
  • 2. 智能指针的使用
    • 智能指针的常见问题
      • 1.使用对象的生命周期去控制资源
      • 2. 像指针一样使用
      • 3. 拷贝问题
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档