浅谈智能指针shared_ptr

          首先什么是智能指针?

      RAII:资源分配即初始化,通俗点来讲,就是定义一个类来封装资源的分配和释放,再构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

      实现机制:是利用类的构造和析构函数(释放资源)是由编译器自动调用的。

      智能指针不仅管理执行对象的释放问题,还可以像指针一样的使用。

      C++标准库中主要有四个智能指针,分别是auto_ptr,shared_ptr,scoped_ptr和weak_ptr。auto_ptr是C++98标准化才引入的,scoped_ptr,shared_ptr和weak_ptr是C++11标准化才引入的。auto_ptr是对资源进行了转移,虽说简单,但使用起来都是坑,因此在任何情况下建议都不要使用;scoped_ptr是资源独占,防拷贝和赋值,将其拷贝函数和赋值操作的重载只给出私有的声明(面试中若写智能指针,可写scoped_ptr简单不易出错);shared_ptr是对资源的共享,可以拷贝和赋值(与weak_ptr结合使用),weak_ptr主要与shared_ptr配合使用,自己不能直接管理动态开辟的空间。

      下面我主要想谈一下shared_ptr。

      首先我们来看一下shared_ptr的代码实现:

template
class del
{
public:
	void operator()(T* _ptr)
	{
		delete _ptr;
		_ptr = NULL;
	}
};

class Fclose
{
public:
	FILE* fclose(FILE* ptr)
	{
		cout << "fclose" << endl;
		fclose(ptr);
	}
};

template
class Free
{
public:
	T* free(T* ptr)
	{
		free(ptr);
		ptr = NULL;
	}
};

template
class shared_ptr
{
public:
	shared_ptr(T* ptr)
		:ptr(_ptr)
		, pcount(_pcount)
	{
		if (ptr != NULL)
		{
			*ptr = new int;
			*pcount = 1;
		}
	}
	shared_ptr(shared_ptr& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		if (_pcount)
		{
			(*_pcount)++;
		}

	}
	~shared_ptr()
	{
		cout << "~shared_ptr()" << endl;
		if (_ptr&&!(--*_pcount))
		{
			destroy();
		}
	}
	shared_ptr& operator=(shared_ptr& sp)
	{
		if (!this == &sp)
		{
			if (_ptr && (--*pcount))
			{
				destroy();
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;
		}
		return *this;
	}
	T* operator->()
	{
		return _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
private:
	void  destroy()
	{
		del()(_ptr);
		delete _pcount;
		_pcount = NULL;
	}
	T* _ptr;
	int* _pcount;
};

          虽然看起来好像没有什么问题了,但shared_ptr还存在三个问题:线程安全问题,需要定制删除器以及循环引用问题。

线程安全问题:

       1.一个shared_ptr实体可被各个线程同时读取;

       2.两个的shared_ptr实体可以被两个线程同时写入,“析构”算写操作;

       3.如果要从各个线程读写同一个shared_ptr对象,那么需要加锁。

定制删除器:

       在shared_ptr中被管理的资源都需要用delete来释放,当被管理的资源若是文件类型,或是用malloc开辟出来的动态空间,则无法使用delete来释放,若不做任何的处理,程序将奔溃,所以,我们要为其定制删除器。上面给出的代码就为其定制了删除器,使程序书写变得麻烦。

循环引用问题:

       我们先来看一段代码:

#define _CRT_SECURE_NO_WARNINGS 1  
#include  
using namespace std;
#include"boost/shared_ptr.hpp"

struct Node
{
	Node(int value)
	:_value(value)
	{
		cout << "Node()" << endl;
	}
	~Node()
	{
		cout << "~Node()" << endl;
	}
	shared_ptr _prev;
	shared_ptr _next;
	int _value;
};

void Test2()
{
	shared_ptr sp1(new Node(1));
	shared_ptr sp2(new Node(2));
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
	sp1->_next = sp2;
	sp2->_prev = sp1;
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;
}

int main()
{
	Test2();
	return 0;
}
     浅谈智能指针shared_ptr_第1张图片

        构建shared_ptr的两个对象sp1,sp2,先构造的后释放,后构造的先释放,先释放的是sp2,那么它的引用计数为2,减去1之后成为1,不能进行释放,因为sp1还在管理这段空间,但是sp2这个变量已经被销毁,因为它是栈上的变量,但sp2管理的堆上的空间并没有释放。接下来,释放sp1,同样,先将引用计数减去1,引用计数变成了1,因此也不会释放sp1管理的动态空间。只有当sp1,sp2引用计数变成0时,空间才允许被释放。也就是说sp2要释放,必须要sp1释放,但sp1要释放,必须要等sp2释放。最终它们两个对象都有释放空间,造成了内存的泄露。

        下面根据下图,我们来理解一下sp1,sp2的构造过程,以及它们是如何利用shared_ptr智能指针来管理空间的。

      浅谈智能指针shared_ptr_第2张图片

       这是使用shared_ptr所构建出来的对象。一部分用来管理引用计数,另一部分用来管理节点。

      浅谈智能指针shared_ptr_第3张图片

       如图,便是上述双向链表最终构建出来的节点指针的指向。

       首先是构造智能指针sp1,先对其引用计数开辟空间,并初始化user和weaks均为1,然后将节点的空间交给__ptr来管理,引用计数的空间交给_Ref来管理。然后在构建sp2,构造过程与sp1相同。代码中sp2->prev=sp1与sp1->next=sp2调用过程相同,都是将各自的引用计数空间中的user加至2,然后sp1中的_next节点中的_Ref管理sp2的引用计数;sp2中的_prev节点中的_Ref管理的sp1的引用计数,_ptr管理sp1节点。析构时,先析构sp2,但引用计数中的user为1,无法释放空间,再去析构sp1,与sp2相同,也无法对空间进行释放。所以,开辟出来的四块动态内存空间,都没有释放。

       这就是shared_ptr的循环引用问题。

       以上就是对shared_ptr进行的讨论,若还有哪些地方没有涉及到欢迎补充。

   

你可能感兴趣的:(C++/C)