智能指针的介绍

本文介绍了C++中的智能指针,包括RAII原则,详细讲解了auto_ptr的缺陷,以及boost库中的智能指针。接着,文章阐述了unique_ptr的不可复制特性及其定制删除器的使用,shared_ptr的计数机制和防止内存泄漏的设计,最后讨论了weak_ptr解决循环引用问题的重要性。

目录

1. 什么是RAII?

2. auto_ptr

3. boost

4. unique_ptr

5. 定制删除器

6. shared_ptr

7. week_ptr


1. 什么是RAII?

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

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

  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效
// 利用RAII思想写出的智能指针模板

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

     ~SmartPtr()
     {
         delete _ptr;
     } 

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

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

private:
     T* _ptr;
}; 

2. auto_ptr

在C++98中就提供了一种类似于智能指针的指针。

auto_ptr的原理是:将管理权转移的思想

具体实现如下:

#pragma once
namespace bit
{
	template<class T>
	class auto_ptr
	{
		//构造函数
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		//转移资源
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}
		//赋值
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				if (_ptr)
				{
					delete _ptr;
				}
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}
		//析构函数
		~auto_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return  _ptr;
		}
	private:
		T* _ptr;
	};
}

这样有一种缺陷就是赋值的指针将会变成空指针,这种效果也遭受了很多学习C++语法人的吐槽,以至于很多公司禁止使用该指针。

这种“智能指针”不能满足我们的要求,因此在C++11出现之前,广泛的C++使用者就聚齐在一起,形成了一个社区:boost

3. boost

什么是boost?

 而我们的智能指针就出自于此,较为著名的智能指针包括:scoped_ptr 、shared_ptr 、week_ptr

 而C++出台的unique_ptr、shared_ptr、week_ptr都出自于此。

4. unique_ptr

什么是unique_ptr?

通过名字我们可以发现它被称为独一无二的指针,也就是说明它是无法被复制的,而它的底层也确实如此。

 通过它的拷贝构造,我们可以发现它没有对象构造和赋值构造,这也就保证了该指针指向的空间只有它一个指向。

那么我们来观察它的底层是如何实现的:

using namespace std;
namespace bit
{
	template<class T>
	struct default_delete
	{
		void operator()(T* ptr)
		{
			delete ptr;
			ptr = nullptr;
		}
	};
	template<class T,class D = default_delete<T>>
	class unique_ptr
	{
	public:
		//构造函数
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		unique_ptr(unique_ptr<T>& ap) = delete;
		//赋值
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
		//析构函数
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete ptr" << endl;
				D d;
				d(_ptr);
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return  _ptr;
		}
	private:
		T* _ptr;
	};
}

在它的底层中将其中两个构造函数加上了delete,在C++11中新增加了delete和default关键字,如果一个函数后加上delete,表示无法默认生成。那么就表明如果不显式写这两个构造函数,那么就能够实现unique这个特点。

至于这里还存在一个叫D的类型,它表示我们存储的指针以什么形式释放

这里我们就称为定制删除器

5. 定制删除器

如果我们传入的值是通过new实现出来的,那么我们在释放的时候就需要通过delete来进行释放

如果是new [],那么我们就需要delete[]来进行释放,所有我们的关键点就是如何在析构的时候释放空间,这里我们就涉及到了定制删除器。

定制删除器顾名思义就是对不同的步骤定制了不同的解决方案,

比如如果传入的值是通过new来实现的,那么就需要使用delete

如果是通过malloc来实现的,那么就需要使用free

比如unique_ptr,我们就可以发现它不仅仅是传入一个T参数,还需要传入一个D参数,这个D参数就是我们的定制删除器,就可以在析构中调用D所形成的对象来释放我们的指针。

6. shared_ptr

 有一种情况:多个指针管理同一块区域,那么我们这块区间是什么时候进行释放?

这里我们就用到了shared_ptr,那么shared_ptr是如何来实现区间的释放呢?

其实是采用了计数的原理,就好比老师让最后一个离开教室的把们关上一样,如果最后只剩下一个指针管理这块区域,那么当对象销毁时,这块空间也随之释放。

shared_ptr的特点:

1. 通过计数来实现多个shared_ptr对象管理同一块资源

2. 当对象被销毁时,将每个资源所指向的计数-1,

3. 如果当前资源的计数为0,则将其释放,如果不为0,则说明还有指针指向当前资源

那么它的基本实现是怎样呢?

#pragma once
namespace bit
{
	template<class T>
	class shared_ptr
	{
		void Release()
		{
			if (--(*_count) && _ptr)
			{
				delete _ptr;
				_ptr = nullptr;
				delete _count;
				_count = nullptr;
			}
		}
		//构造函数
		shared_ptr(T* ptr)
			:_ptr(ptr),
			_count(new int(1))
		{}
		//转移资源
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr),
			_count(sp._count)
		{
			++(*_count);
		}
		//赋值
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();
				_ptr = sp._ptr;
				_count = sp._count;
				*(_count)++;
			}
			return *this;
		}
		//析构函数
		~shared_ptr()
		{
			Release();
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return  _ptr;
		}
		T* get()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _count;
	};
}

这里最为关键的就是Release函数,通过该函数我们可以得知是否所指向的资源要释放,这也是shared_ptr的灵魂所在。

7. week_ptr

那么shared_ptr就是完美了吗?

在不整花活的情况下,使用shared_ptr是完全足够的,但是总会有特殊情况

 如果我们要实现这样的情况,会发生什么呢?

我们发现在程序终止时,没有打印出析构函数里面的字符串,因为我们可以发现没有当p1、p2出了作用域后,没有进行销毁,产生了内存泄漏。

那么这是怎么发生了呢?

 

 

 那么当p1、p2释放后,计数值还是1,_next的释放取决于_prev释放,而_prev的释放又取决于_next,那么这样相互依赖的关系被称为循环引用。

那么我们是如何来避免进行循环引用的呢?,这里C++11就提出了一个解决方案:引入了一个新指针-- week_ptr,由于我们的计数的由于_next和_prev指向了空间,导致我们的计数+1,又因为我们想让_next和_prev指向空间,因为我们可以当它指向空间时。当前空间不计数,这也是week_ptr的原理,也就是小弟帮大哥办事。

namespace bit
{
	template <class T>
	class week_ptr
	{
	public:
		week_ptr(const T* ptr)
			:_ptr(ptr)
		{}
		week_ptr(const week_ptr<T>& wp)
			:_ptr(wp._ptr)
		{}
		week_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}
		void operator = (const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
		}
	private:
		T* _ptr;
	};
}

只干事没有实权,这就是小弟应该做的事!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值