探索智能指针

Posted ych9527

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了探索智能指针相关的知识,希望对你有一定的参考价值。

1.为什么需要智能指针

程序员自己控制的内存,在一些特殊场景之下,有可能发生内存泄漏,智能指针则可以帮助对内存进行管理

智能指针采用的RAII思想 -> 利用对象的生命周期来控制程序资源,在对象构建的时候获取资源,在对象析构的时候释放资源,即将对资源的管理工作,托付给了一个对象,这样做的好处是:不需要显示的释放资源、对象所需要的资源,在其生命周期内始终保持有效

template<class T>
class smartPtr
{
public:
	
	smartPtr(T *ptr)
		:_Ptr(ptr)
	{}

	~smartPtr()
	{
		cout << "~smartPtr()" << endl;
		delete[] _Ptr;
	}

private:
	T *_Ptr;
};

void test()
{
	while (1)
	{
		int *ptr = new int;

		//使用ARII思想,管理资源,如果没有,当抛异常的时候,并不会释放资源
		//就会导致内存泄漏
		smartPtr<int> sptr(ptr);
		throw 1;

		//delete ptr;
	}
}

void Ptrtest()
{
	while (1)
	{
		try
		{
			test();
		}

		catch (...)
		{
			cout << "catch (...) " << endl;
		}
	}
}

2.三种智能指针使用及原理分析

2.1 auto_ptr

1.使用立即初始化,防止二次释放

在这里插入图片描述

2.拷贝和赋值会导致资源管理权转移,再次解引用就会造成空指针的访问

在这里插入图片描述
3.由于auto_ptr 的拷贝和赋值拷贝都会出现资源转移的情况,即使用的时候很容易出错,因此实际之中,一般不允许使用

4.模拟实现

template<class T>
class Auto_ptr
{
public:

	Auto_ptr(T *ptr)
		:_ptr(ptr)
	{}

	~Auto_ptr()
	{
		cout << "~Auto_ptr()" << endl;
		delete[] _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;
	}

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

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

private:
	T *_ptr;
};



class Data
{
public:
	Data(int day)
		:_day(day)
	{}

	int _day;
};

2.2 unique_ptr

既然auto_ptr的拷贝和赋值会发生资源转移,那么unique_ptr,就禁止拷贝和赋值的发生,即在拷贝和赋值成员函数后面加上delete
在这里插入图片描述

模拟实现:

template<class T>
class Unique_ptr
{
public:

	Unique_ptr(T *ptr)
		:_ptr(ptr)
	{}

	~Unique_ptr()
	{
		delete[] _ptr;
	}

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

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

	//禁止拷贝和赋值
	Unique_ptr(Unique_ptr<T>& up)=delete;
	Unique_ptr<T>& operator =(Unique_ptr<T>& up)=delete;

private:
	T *_ptr;
};

2.3shared_ptr

2.3.1原理

auto_ptr 的拷贝和赋值存在不安全因素,unnique_ptr直接禁止了拷贝和赋值

如果智能指针需要拷贝和赋值呢,这时候,就得看shared_ptr了
在这里插入图片描述

如何实现:
我们可以给定一个资源计数器,当前资源被多少个智能指针管理,计数器则为几,当进行析构的时候,计数器–,当计数器为0的时候,表示当前资源没有被管理了,则对该资源进行delete

template<class T>
class Shared_ptr
{
public:

	Shared_ptr(T *ptr)
		:_ptr(ptr)
		, _CountPtr(new int(1))//构造的时候,被一个管理起来
	{}

	~Shared_ptr()
	{
		(*_CountPtr)--;

		if (*_CountPtr == 0)//为0时,表示没有管理的了
		{
			delete _ptr;
			delete _CountPtr;
		}
	}

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

	Shared_ptr<T> &operator=(const Shared_ptr<T> &sp)
	{
		//判断是不是管理的同一份资源
		if (_ptr != sp._ptr)
		{
			if (--(*_CountPtr) == 0)//断开已经管理的资源
			{
				delete _ptr;//如果管理的资源没有其它人进行管理了,则释放掉
				delete _CountPtr;
			}

			//赋值
			_ptr = sp._ptr;
			_CountPtr = sp._CountPtr;
			(*_CountPtr)++;
		}

		return *this;
	}

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

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

	int GetCount()
	{
		return *_CountPtr;
	}

private:
	T *_ptr;
	int *_CountPtr;//通过指针,来控制管理同一份资源的,都指向同一块地址空间
};

2.3.2线程安全问题

多线程之中,在进行拷贝和赋值的时候,线程计数器就变成了临界资源,这时候,就需要对线程计数器进行加锁处理,否则会发生线程安全问题

class Data
{
public:
	Data(int day)
		:_day(day)
	{}

	int _day;
};

void func(Shared_ptr<Data> &sp,int n)
{

	for (int i = 0; i < n; i++)
	{
		Shared_ptr<Data> sp2(sp);//进行n次拷贝
	}
}


void test()
{
	Shared_ptr<Data> sp(new Data(1000));
	cout << "begin:" << sp.GetCount() << endl;

	int n;
	cin >> n;


	thread t1(func, ref(sp), n);//使用ref线程函数的参数是以值拷贝的方式到线程栈空间的
	thread t2(func, ref(sp), n);

	t1.join();
	t2.join();

	cout << "end:" << sp.GetCount() << endl;
}

在这里插入图片描述

加锁后的代码和实验效果:

template<class T>
class Shared_ptr
{
public:

	Shared_ptr(T *ptr)
		:_ptr(ptr)
		, _CountPtr(new int(1))//构造的时候,被一个管理起来
		, _mtx(new mutex)//初始化一把锁
	{}

	~Shared_ptr()
	{
		subRef();//加锁后的--操作
		//(*_CountPtr)--;

		if (*_CountPtr == 0)//为0时,表示没有管理的了
		{
			delete _ptr;
			delete _CountPtr;
			delete _mtx;
			cout << "~Shared_ptr()" << endl;
		}
	}

	Shared_ptr(const Shared_ptr<T> &sp)
		:_ptr(sp._ptr)
		, _CountPtr(sp._CountPtr)
		, _mtx(sp._mtx)
	{
		addRef();//加锁后的++操作
		//(*_CountPtr)++;
	}

	Shared_ptr<T> &operator=(const Shared_ptr<T> &sp)
	{
		//判断是不是管理的同一份资源
		if (_ptr != sp._ptr)
		{
			//if (--(*_CountPtr) == 0)//断开已经管理的资源
			if (subRef==0)
			{
				delete _ptr;//如果管理的资源没有其它人进行管理了,则释放掉
				delete _CountPtr;
				delete _mtx;
			}

			//赋值
			_ptr = sp._ptr;
			_CountPtr = sp._CountPtr;
			_mtx = sp._mtx;
			addRef();
		}

		return *this;
	}

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

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

	int GetCount()
	{
		return *_CountPtr;
	}

	int addRef()
	{
		_mtx->lock();
		(*_CountPtr)++;
		_mtx->unlock();

		return *_CountPtr;
	}

	int subRef()
	{
		_mtx->lock();
		(*_CountPtr)--;
		_mtx->unlock();

		return *_CountPtr;
	}

private:
	T *_ptr;
	int *_CountPtr;//通过指针,来控制管理同一份资源的,都指向同一块地址空间
	mutex *_mtx;//加上一把锁
};

在这里插入图片描述

2.3.3循环引用问题

template <class T>
struct ListNode
{
	shared_ptr<ListNode<T>> _next;
	shared_ptr<ListNode<T>> _prev;

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

void test()
{
	shared_ptr<ListNode<int>> sp(new ListNode<int>);
	shared_ptr<ListNode<int>> sp2(new ListNode<int>);
	
	cout << "begin:" << sp.use_count() << " " << sp2.use_count() << endl;
	sp->_next = sp2;
	sp2->_prev = sp;
	cout << "end:" << sp.use_count() << " " << sp2.use_count() << endl;
}

在这里插入图片描述

解决办法:

使next和prev管理资源的时候,计数器不会增加,这样就不会出现内存泄漏的问题了
于是有了weak_ptr智能指针,不会增加计数器

emplate <class T>
struct ListNode
{
	weak_ptr<ListNode<T>> _next;//使用weak_ptr,不会增加计数器的值
	weak_ptr<ListNode<T>> _prev;

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

在这里插入图片描述

2.3.4删除问题

shared_ptr 析构函数中资源的释放,默认是使用delete的,如果我们的对象时malloc出来的,或者是new出来的一整块空间,该怎么办呢?
shared_ptr之中提供了一个删除器(仿函数),可以通过自己传入的仿函数进行资源的释放

template<class T,class Del>
class Shared_ptr
{
public:

	Shared_ptr(T *ptr,Del del)
		:_ptr(ptr)
		, _CountPtr(new int(1))//构造的时候,被一个管理起来
		, _mtx(new mutex)//初始化一把锁
		, _del(del)
	{}

	~Shared_ptr()
	{
		subRef();//加锁后的--操作
		//(*_CountPtr)--;

		if (*_CountPtr == 0)//为0时,表示没有管理的了
		{
			//delete _ptr;
			_del(_ptr);//使用删除器来释放资源
			delete _CountPtr;
			delete _mtx;
			cout << "~Shared_ptr()" << endl;
		}
	}

	Shared_ptr(const Shared_ptr<T,Del> &sp)
		:_ptr(sp._ptr)
		, _CountPtr(sp._CountPtr)
		, _mtx(sp._mtx)
	{
		addRef();//加锁后的++操作
		//(*_CountPtr)++;
	}

	Shared_ptr<T, Del> &operator=(const Shared_ptr<T, Del> &sp)
	{
		//判断是不是管理的同一份资源
		if (_ptr != sp._ptr)
		{
			//if (--(*_CountPtr) == 0)//断开已经管理的资源
			if (subRef==0)
			{
				del(ptr);//使用删除器来释放资源
				//delete _ptr;//如果管理的资源没有其它人进行管理了,则释放掉
				delete _CountPtr;
				delete _mtx;
			}

			//赋值
			_ptr = sp._ptr;
			_CountPtr = sp._CountPtr;
			_mtx = sp._mtx;
			addRef();
		}

		return *this;
	}

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

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

	int GetCount()
	{
		return *_CountPtr;
	}

	int addRef()
	{
		_mtx->lock();
		(*_CountPtr)++;
		_mtx->unlock();

		return *_CountPtr;
	}

	int subRef()
	{
		_mtx->lock();
		(*_CountPtr)--;
		_mtx->unlock();

		return *_CountPtr;
	}

private:
	T *_ptr;
	int *_CountPtr;//通过指针,来控制管理同一份资源的,都指向同一块地址空间
	mutex *_mtx;//加上一把锁
	Del _del;//删除其
};



class A
{
public:
	A()
	{}

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

template<class T>//定制删除器
struct Del
{
	void operator()(T* a)
	{
		delete[] a;
	}

};
void test()
{
	Del<A> del;
	Shared_ptr<A,Del<A>> sp(new A[10],del);
}

在这里插入图片描述

3.总结

智能指针的实现:RAII思想,函数重载模拟指针功能

1.auto_ptr:
发生拷贝或者赋值时,管理权转移
潜在问题:发生拷贝之后,不能继续访问资源,会有解引用问题
一般禁止使用

2.unique_ptr:
防拷贝、防赋值
可以使用:不涉及智能指针拷贝和赋值问题

3.shared_ptr:
通过引用计数,解决拷贝问题
拷贝式,引用计数自增
析构时,引用计数自减,自减之后,如果引用计数器为0,则释放资源
特殊场景之下,会发生循环引用问题,通过weak_ptr解决
shared_ptr自身是线程安全的,但是管理的资源需要用户自己保证线程安全
shared_ptr 默认通过delete释放资源,也可以用户传入仿函数进行删除

以上是关于探索智能指针的主要内容,如果未能解决你的问题,请参考以下文章

指针辨析:悬垂指针哑指针野指针智能指针

c++智能指针介绍_再补充

探索android系统中的强指针实现

探索android系统中的强指针实现

探索android系统中的强指针实现

python 用于数据探索的Python代码片段(例如,在数据科学项目中)