【c++】智能指针——实现内存的自我回收机制

目录

一、了解智能指针      

1、 智能指针:c++的自我内存回收机制的实现

      2、  智能指针是面向对象的指针,以对象的形式做指针的事;

二、介绍几种智能指针

  1、auto_ptr

2、scope_ptr

3、shared_ptr 强智能指针 带有引用计数的智能指针

4、weak_ptr 弱智能指针 可以解决强智能指针相互引用的问题


一、了解智能指针      

1、 智能指针:c++的自我内存回收机制的实现

        在以前的学习中,对于堆上内存的使用,我们一般使用new和delete来进行开辟内存和用完之后的释放内存。但总会在不经意的时候忘记delete,导致内存泄露。了解一点java语言应该知道,在java中只有new没有delete。因为java有自己的垃圾回收机制。

那么,对于内存的回收机制应该怎样理解 
         首先看这样一句代码:
          int *p = new int;
        分析这句代码:首先在对上new一个四字节的空间,p作为一个指针变量,属于栈上资源,它对堆上开辟的内存具有所有权即包括管理权和释放权。即就是说p管理对上开辟的资源。如图所示:

【c++】智能指针——实现内存的自我回收机制_第1张图片

        栈上的资源是系统开辟,系统释放的;而堆上的资源是一般情况下是用户开辟用户释放。那么,要实现内存的自我回收机制即就是要做到用户开辟,系统释放。主要目的是为了防止内存泄露。因此,在这里c++利用智能指针来完成。

      2、  智能指针是面向对象的指针,以对象的形式做指针的事;

        先来看看对象的生成和销毁:

        对象的生成有两步:1.开辟空间;
                                            2.调用构造函数进行初始化;
       对象的销毁有两步:1.调用析构函数释放资源;
                                            2.释放空间;
        以一个对象管理对上开辟的资源,这个对象含有对堆上开辟的内存的所有权,即做着上图的p的事情;

        在我们的标准库里边有这几种智能指针:auto_ptr;当然在boost库里,有这么几种:scope_ptr(又被称为unique_ptr),scope_arr,shared_ptr,shared_arr,weak_ptr,weak_arr等我主要学习了auto_ptr、score_ptr、shared_ptr、weak_ptr这几种智能指针。因此本文也会着重介绍这几种常用的智能指针。分析每种智能指针解决了什么问题?它的缺点?以及哪种新的智能指针解决上个遗留的问题等等。

二、介绍几种智能指针

  1、auto_ptr

  该智能指针主要解决的问题是保证所有权唯一;

  首先分析这样一个过程,有三个对象生成,他们对同一块对内都具管理权权;如图:【c++】智能指针——实现内存的自我回收机制_第2张图片

sp1、sp2、sp3对内存都有所有权,即都可以操作开辟的内存单元。若当sp3结束释放资源后,会将堆上内存还回,则sp1、sp2会指向非法内存,当sp1或者sp2释放时,程序会崩溃,因为释放的是不可访问的内存;所以,对于这种情况的解决方法就是保证其对象的所有权唯一,如下图所示:

【c++】智能指针——实现内存的自我回收机制_第3张图片

sp1对该堆上开辟的资源具有所有权,当sp2要指向并管理该堆上内存时,收回sp1的所有权,将权利赋给sp2;这样就保证了所有权的唯一;

对于解决这一问题的简单实现如下:(模拟实现简单的auto_ptr)

/*
    代码一:
    简单的auto_ptr
*/
#include
using namespace std;
template
class Auto_Ptr
{
public:
	Auto_Ptr(T *ptr = NULL):mptr(ptr){};
	Auto_Ptr(const Auto_Ptr& rhs) :mptr(rhs.mptr)//拷贝构造函数
	{
		rhs.Release();
	}
	Auto_Ptr& operator=(const Auto_Ptr& rhs)//赋值运算符的重载函数
	{
		if(this != &rhs)
		{
			this->~Auto_Ptr();
			mptr = rhs.mptr;
			rhs.Release();
		}
		return *this;
	}
	T& operator*()//解引用运算符重载
	{
		return *mptr;
	}
	T* operator->()//指向运算符重载
	{
		return mptr;
	}
	~Auto_Ptr()//析构函数
	{
		delete mptr;
		mptr = NULL;
	}
private:
	void Release()const //将mptr置空
	{
		(T*)mptr = NULL;
	}
	T *mptr;
};

int main()
{
	Auto_Ptr sp1(new int);//sp1指向开辟的空间,拥有所有权
	Auto_Ptr sp2 = sp1;//sp2指向sp1指向的空间,此时sp1的指向为空,收回所有权,归sp2所有
	sp2 = sp1;//由于sp1此时为空,因此,赋值后,sp2也为空
	*sp1 = 10;//sp1指向为空,因此不能进行写入;
	return 0;
}

       对于auto_ptr这样简单的智能指针的实现,当多个智能指针对象指向同一个堆内存时就会导致其他的智能指针失效,当在其他部分需要使用时,则会导致程序崩溃。导致这样的结果主要是因为智能指针的管理权唯一。

        改进的auto_ptr,上述的auto_ptr保证所有权唯一,但是当两个指针都要对原所拥有的内存进行操作时,就会进行频繁的权限转移;因此,将所有权分为两部分,一是管理权二是释放权;只要保证其释放权唯一即可;如下图所示:

【c++】智能指针——实现内存的自我回收机制_第4张图片

        都可以对内存进行管理,但只有sp1一个有释放权;也就是在改进的auto_ptr中,增加一个标志位,标志是否具有释放权;所有指向同一块内存的对象都对其具有管理权,但是只有sp1有释放权;

        当用已存在的只能指针进行拷贝构造新的只能指针,应该符合这样的规则:旧的智能指针有释放权,新的就有;旧的只能只能没有释放权,新的就没有。

改进的auto_ptr的模拟实现:

/*
    代码二
    改进的auto_ptr 释放权唯一
*/
#include
using namespace std;
template
class Smart_Ptr
{
public:
	Smart_Ptr(T* ptr) :mptr(ptr), flag(true){}
	Smart_Ptr(const Smart_Ptr& rhs) :mptr(rhs.mptr)
	{
		flag = rhs.flag;
		rhs.flag = false;
	}
	Smart_Ptr& operator=(const Smart_Ptr& rhs)//赋值运算符的重载函数
	{
		if(this != &rhs)
		{
			this->~Smart_Ptr();
			mptr = rhs.mptr;
			rhs.Release();
			rhs.flag = false;
		}
		return *this;
	}


	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}
	~Smart_Ptr()
	{
		if (flag)
		{
			delete mptr;
		}
		mptr = NULL;
	}
private:
	T *mptr;
	mutable bool flag;//释放权
};
void Test(Smart_Ptr sp)
{
}
int main()
{
	Smart_Ptr sp1(new int);//sp1指向开辟的内存块,sp1拥有释放权
	Smart_Ptr sp2(sp1);//利用sp1拷贝构造生成sp2,sp1释放权转移到sp2
	*sp1 = 10;//可操作,sp1仍具有管理权
	return 0;
}

 

        当某个智能指针进行提前释放时,会导致其他的智能指针出现错误;

2、scope_ptr(unique_ptr)

        不允许权限转移,将权限转移的构造函数和拷贝构造函数屏蔽起来,即就是一个智能指针指向一个内存块。解决权限转移带来的弊端;如图所示:

【c++】智能指针——实现内存的自我回收机制_第5张图片

模拟实现scope_ptr的程序如下:

/*
    代码三
    scope_ptr
*/
#include
using namespace std;
template
class Scope_Ptr
{
public:
	Scope_Ptr(T* ptr) :mptr(ptr){}//构造函数
	~Scope_Ptr()//析构
	{
		delete mptr;
	}
	T& operator*()//解引用运算符重载
	{
		return *mptr;
	}
	T* operator->()//指向运算符重载
	{
		return mptr;
	}
private:
	Scope_Ptr(const Scope_Ptr&);//拷贝构造
	Scope_Ptr& operator=(const Scope_Ptr&);//赋值运算符的重载函数
	T* mptr;
};

int main()
{
	int* p = new int;
//人为控制让sp1、sp2、sp3指向同一个内存块,三者的操作无法正常进行
	Scope_Ptr sp1(p);
	Scope_Ptr sp2(p);
	Scope_Ptr sp3(p);

	return 0;
}

        当外部人为控制多个智能指针指向同一个内存块时,无法进行正确操作(即当一个智能指针进行释放后,会销毁堆内存使得其余的智能指针称为野指针);并且,一个智能指针指向一个内存块,可能导致内存浪费严重等问题;

3、shared_ptr 强智能指针 带有引用计数的智能指针

shared_ptr智能指针是加入引用计数:代表有多少个智能指针对象管理这个堆内存;

因此强智能指针的实现分为两部分:
      1、实现一个引用计数管理器:增加或者减少引用计数、获取引用计数
              addRef();
              delRef();
              getRef();
         2、实现智能指针类:堆内存的开辟或释放
shared_ptr的存储分为两部分,地址域和引用计数域,如下图所示:

【c++】智能指针——实现内存的自我回收机制_第6张图片
        增加智能指针时:获取到智能指针所指向的内存地址后,先在引用计数管理器中匹配有无该地址,有,则给对应的引用计数加1;无,增加地址域并将对应的引用计数置1; 
        释放智能指针时:先判断引用计数,大于0时,减引用计数;小于0,时,抛出异常;

shared_ptr的简单实现,这里将引用计数管理器设置为单例模式,因为任何类型都可以用一个引用计数器来进行管理。

/*
    代码四
    shared_ptr
*/
#include
using namespace std;
class RefManage
{
public:
	static RefManage* getINstance()
	{
		return &rm;
	}
private:
	RefManage():length(0)
	{
	}
	RefManage(const RefManage&rhs);
public:
	void addRef(void* ptr)//获取引用计数
	{
		if(ptr != NULL)
		{
			int index = Find(ptr);
			if(index < 0)
			{
				/*
				arr[length].addr = ptr;
				arr[length].refcount++;
				*/
				Node tmp(ptr,1);
				arr[length++] = tmp;
			}
			else
			{
				arr[index].refcount++;
			}
		}
	}
	void delRef(void* ptr)//删除引用计数
	{
		if(ptr != NULL)
		{
			int index = Find(ptr);
			if(index < 0)
			{
				throw exception("addr is not exist!");
			}
			else
			{
				if(arr[index].refcount != 0)
				{
					arr[index].refcount--;
				}
			}
		}
	}
/*
	1.ptr == NULL ==> return 0
	2.ptr != null ==>找不到 -1
	3.               找到 ref
*/
	int getRef(void* ptr)//获得引用计数
	{
		if(ptr == NULL)
		{
			return 0;
		}
		int index = Find(ptr);
		if(index < 0)
		{
			return -1;
		}
		else
		{
			return arr[index].refcount;
		}
	}
private:
	int Find(void* mptr)
	{
		for(int i = 0;i < length ; i++)
		{
			if(arr[i].addr == mptr)
			{
				return i;
			}
		}
		return -1;
	}
	class Node
	{
	public:
		Node(void* ptr,int ref = 0)
		{
			addr = ptr;
			refcount = ref;
		}
		Node()
		{
			memset(this,0,sizeof(Node));
		}
	public:
		void *addr;
		int refcount;
	};
	Node arr[10];//不可扩容的
	int length;//有效结点个数  当前要插入的节点下标
	static RefManage rm;
};
RefManage RefManage::rm;
template
class Shared_Ptr
{
public:
	Shared_Ptr(T* ptr):mptr(ptr)
	{
	}
	Shared_Ptr(const Shared_Ptr& rhs):mptr(rhs.mptr)
	{
		AddRef();
	}
	Shared_Ptr& operator=(const Shared_Ptr& rhs)
	{
		if(this != &rhs)
		{
			DelRef();
			if(GetRef() == 0)
			{
				delete mptr;
			}
			mptr = rhs.mptr;
			AddRef();
		}
		return *this;
	}
	~Shared_Ptr()
	{
		DelRef();
		if(GetRef() == 0)
		{
			delete mptr;
		}
		mptr = NULL;
	}
	T* operator->()
	{
		return mptr;
	}
	T& operator*()
	{
		return *mptr;
	}
private:
	void AddRef()
	{
		rm->addRef(mptr);
	}
	void DelRef()
	{
		rm->delRef(mptr);
	}
	int GetRef()
	{
		return rm->getRef(mptr);
	}
	T* mptr;
	static RefManage* rm;
};
template
RefManage* Shared_Ptr::rm = RefManage::getINstance();
 
int main()
{
	int *p = new int;
	Shared_Ptr sp1(p);
	Shared_Ptr sp2(p);
	Shared_Ptr sp3(p);
	return 0;
}

强智能指针存在的问题,先看以下的代码:

/*
    代码五
    强智能指针的相互引用问题
*/
class B;
class A
{
public:
	A()
	{
		cout<<"A::A()"< pb;
};
class B
{
public:
	B()
	{
		cout<<"B::B()"< pa;
};
int main()
{
	Shared_Ptr spa (new A());
	Shared_Ptr spb (new B());
	spa->pb = spb;
	spb->pa = spa;
	return 0;
}

分析结果如图所示:

【c++】智能指针——实现内存的自我回收机制_第7张图片

         spa->pb = spb;
         spb->pa = spa;
       这两句实现了智能指针之间的相/互引用,相互引用之后引用计数就会加1。那么在销毁的时候引用计数就不会为0;
       因此,强智能指针存在的问题就是相互引用之后就会导致对象无法销毁。
这里我们引入弱智能指针,先来看强弱智能指针的对比:
        强智能指针;一旦有引用,引用计数都会加
        弱智能指针:weak_ptr 只做管理,其他什么事都不会做;因此不能单独使用应该结合强智能指针使用,并且不加引用计数;弱智能指针单独使用的话会导致内存泄露,往往配合强智能指针一起使用。

4、weak_ptr 弱智能指针 可以解决强智能指针相互引用的问题

弱智能指针的简单实现:

/*
    代码六
    weak_ptr
*/
template
class Weak_ptr
{
public:
	Weak_ptr(T* ptr) : mptr(ptr)
	{}
	~Weak_ptr()
	{
	}
	Weak_ptr(const Weak_ptr&rhs):mptr(rhs.mptr)
	{
	}
	//this-->weak_ptr        rhs-->shared_ptr
	Weak_ptr& operator = (const Weak_ptr&rhs)
	{
		rhs.GetPtr();//GetPtr();shared_ptr中的接口
		return *this;
	}
	T* operator->()
	{
		return mptr;
	}
	T& operator*()
	{
		return *mptr;
	}
private:
	T* mptr;
};

利用强弱指针结合使用,解决智能指针之间的相互引用问题;当智能指针相互引用时,只进行管理,对引用的内存的引用计数不进行加操作;

小结:
智能指针是面向对象的指针,以对象的形式做指针的事;
1、auto_ptr:
    管理权唯一
    释放权唯一:解决当多个智能指针对象指向同一个堆内存时就会导致其他的智能指针失效,当在其他部分需要使用时导致程序崩溃的问题
2、scope_ptr
    一个智能指针管理一个内存块:解决权限转移带来的弊端
3、shared_ptr
    强智能指针 增加引用计数 :解决scope_ptr带来的内存浪费等问题
4、weak_ptr
    弱智能指针 不能单独使用 和强智能指针配合使用 解决强智能指针中相互引用的问题

你可能感兴趣的:(c++)