智能指针

一、为什么要引入智能指针?


    我们知道,C++中的动态内存需要用户自己来维护,动态开辟的空间,在出函数作用域或者程序正常退出前必须释放掉,否则会造成内存泄露,虽然有时我们已经非常谨慎了,然而当代吗比较长或比较复杂时,我们仍然可能忘记释放,正所谓防不胜防,一起看一段代码:

void FunTest1()
{
	int *p = new int[10];
	FILE* pFile = fopen( "1.txt", "r" );//已读的方式打开"1.txt"文件
	if (pFile == NULL)//如果打开失败,则返回
	{
		return;//error:如果打开失败,则造成内存泄漏
	}
	// DoSomethint();
	if (p != NULL)
	{
		delete[] p;
		p = NULL;
	}
}
void FunTest2()
{
	int *p = new int[10];
	try
	{
		//DoSomething();
	}
	catch (...)
	{
		return;//返回之前忘记释放内存,导致内存泄漏
	}
	delete[] p;
}

    从上述两个例子中可以看出,在动态申请空间中,每一次返回之前都必须对动态内存进行释放,否则就内存泄漏,这样做不仅麻烦,还容易忘记,那么,怎样解决上述问题呢?就不得不引入我们的新概念---->智能指针(auto_ptr)


 

二、智能指针的基本情况


 1、资源分配即初始化RAII(Resource Acquisition Is Initialization):定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放


 2、Boost库的智能指针 

(ps:新的C++11标准中已经引入了unique_ptr/shared_ptr/weak_ptr)



 三、模拟实现库中的某些智能指针 


1、新库中的智能指针---->auto_ptr 

(1)单纯管理空间

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap)
		:_p(ap)
	{
		ap = NULL;//让ap与动态申请的空间脱离关系
		cout<<"AutoPtr()"<<endl;
	}
	~AutoPtr()
	{
		cout<<"~AutoPtr()"<<endl;
		if(NULL != _p)
		{
			delete _p;
			_p = NULL;
		}
	}
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p = new int;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}


运行结果:



(2)拷贝对象

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap)
		:_p(ap)
	{
		ap = NULL;//让ap与动态申请的空间脱离关系
		cout<<"AutoPtr()"<<endl;
	}
	~AutoPtr()
	{
		if(NULL != _p)
		{
			cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
		}
	}
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p = new int;
	AutoPtr<int> p1(p);
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}
ERROR:同一块被释放了两次,导致程序崩溃。 


改进:添加拷贝构造函数

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap)
		:_p(ap)
	{
		ap = NULL;//让ap与动态申请的空间脱离关系
		cout<<"AutoPtr()"<<endl;
	}
	AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
		:_p(ap._p)
	{
		ap = NULL;//让ap与动态申请的空间脱离关系
	}
	~AutoPtr()
	{
		if(NULL != _p)
		{
		    cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
		}
	}
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p = new int;
	AutoPtr<int> p1(p);
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}


运行结果:



(3)构造函数的参数要给缺省值

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap)
		:_p(ap)
	{
		ap = NULL;//让ap与动态申请的空间脱离关系
		cout<<"AutoPtr()"<<endl;
	}
	AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
		:_p(ap._p)
	{
		ap = NULL;//让ap与动态申请的空间脱离关系
	}
	~AutoPtr()
	{
		if(NULL != _p)
		{
		    cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
		}
	}
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p = new int;
	AutoPtr<int> p1(p);
	AutoPtr<int> p2;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}


error:



改进:

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = NULL)
		:_p(ap)
	{
		ap = NULL;//让ap与动态申请的空间脱离关系
		cout<<"AutoPtr()"<<endl;
	}
	AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
		:_p(ap._p)
	{
		ap = NULL;//让ap与动态申请的空间脱离关系
	}
	~AutoPtr()
	{
		if(NULL != _p)
		{
		    cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
		}
	}
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p = new int;
	AutoPtr<int> p1(p);
	AutoPtr<int> p2;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}

(4)赋值运算符重载

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = NULL)
		:_p(ap)
	{
		cout<<"AutoPtr()"<<endl;
	}

	AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
		:_p(ap._p)
	{
		ap._p = NULL;//让ap与动态申请的空间脱离关系
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap)
	{
		if(this != &ap)
		{
			if(NULL != _p)//防止对空对象进行引用导致程序崩溃
			{
				delete _p;
				_p = ap._p;//如果_p为空,则崩溃
				ap._p = NULL;//脱离关系
			}
		}
		return *this;
	}

	~AutoPtr()
	{
		if(NULL != _p)
		{
			cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
		}
	}
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p = new int;
	AutoPtr<int> p1(p);
	AutoPtr<int> p2;
	p2 = p1;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}


运行过程截图:



运行结果:



(5)获取原生指针,重载*和->

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = NULL)
		:_p(ap)
	{
		cout<<"AutoPtr()"<<endl;
	}

	AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
		:_p(ap._p)
	{
		ap._p = NULL;//让ap与动态申请的空间脱离关系
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap)
	{
		if(this != &ap)
		{
			if(NULL != _p)//防止对空对象进行引用导致程序崩溃
			{
				delete _p;
				_p = ap._p;//如果_p为空,则崩溃
				ap._p = NULL;//脱离关系
			}
		}
		return *this;
	}

	~AutoPtr()
	{
		if(NULL != _p)
		{
			cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
		}
	}
	T* Get()
	{
		return _p;
	}
	T* operator*()
	{
		return *_p;
	}
	T* operator->()
	{
		return _p;
	}
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p = new int;
	AutoPtr<int> p1(p);
	AutoPtr<int> p2;
	p2 = p1;
	AutoPtr<int>* p3 = &p1;
	cout<<*(p3->Get)()<<endl;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}


运行过程截图: 


运行结果: 



auto_ptr致命缺陷:只能由一个对象管理所开辟的空间


2、老库中维护一个布尔变量owner实现---->owner_ptr


实现原理:构造对象时,将该对象的_owner赋值为TRUE,析构对象时,只有当该对象的_owner值为TRUE才释放,否则不释放

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = NULL)
		:_p(ap)
		,_owner(true)//说明这块空间已有对象占用
	{
		cout<<"AutoPtr()"<<endl;
	}

	AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
		:_p(ap._p)
		,_owner(true)
	{
		ap._owner = false;//让ap与动态申请的空间脱离关系
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap)
	{
		if(this != &ap)
		{
			if(NULL != _p)//防止对空对象进行引用导致程序崩溃
			{
				delete _p;
				_p = ap._p;//如果_p为空,则崩溃
				_owner = true;
				ap._owner = false;//资源转移
			}
		}
		return *this;
	}

	~AutoPtr()
	{
		if(_owner &&NULL != _p)
		{
			cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
			_owner = false;//释放完后将_owner赋值为false
		}
	}
	T* Get()
	{
		return _p;
	}
	T* operator*()
	{
		return *_p;
	}
	T* operator->()
	{
		return _p;
	}
private:
	T* _p;
	bool _owner;
};
void Funtest()
{
	AutoPtr<int> p = new int;
	AutoPtr<int> p1(p);
	AutoPtr<int> p2;
	p2 = p1;
	AutoPtr<int>* p3 = &p1;
	cout<<*(p3->Get)()<<endl;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}

运行结果:



不足之处:

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = NULL)
		:_p(ap)
		,_owner(true)//说明这块空间已有对象占用
	{
		cout<<"AutoPtr()"<<endl;
	}

	AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
		:_p(ap._p)
		,_owner(true)
	{
		ap._owner = false;//让ap与动态申请的空间脱离关系
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap)
	{
		if(this != &ap)
		{
			if(NULL != _p)//防止对空对象进行引用导致程序崩溃
			{
				delete _p;
				_p = ap._p;//如果_p为空,则崩溃
				_owner = true;
				ap._owner = false;//资源转移
			}
		}
		return *this;
	}

	~AutoPtr()
	{
		if(_owner &&NULL != _p)
		{
			cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
			_owner = false;//释放完后将_owner赋值为false
		}
	}
	T* Get()
	{
		return _p;
	}
	T* operator*()
	{
		return *_p;
	}
	T* operator->()
	{
		return _p;
	}
private:
	T* _p;
	bool _owner;
};
void Funtest()
{
	AutoPtr<int> sp1;
	if(true)
	{
		AutoPtr<int> sp2(sp1);
	}//出了作用域sp1就成为野指针
	AutoPtr<int> sp3;
	sp3 = sp1;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}


运行过程截图:


运行结果:




总结:以上两种方式我们都是通过资源转移来防止一块空间被释放多次,auto_ptr的缺点是申请的空间最终只能由一个对象来管理,而owner_ptr虽然可通过多个对象管理空间,但却可能导致野指针等问题,这并不是我们的初衷,那么有什么更好的实现方法呢?


 

3、单个对象独占空间---->scoped_ptr 


(1)首先思考一个问题,一个类如何防止被拷贝疑问疑问疑问

class B
{
public:
	B()
	{
		cout<<"B()"<<endl;
	}
private:
	B(const B& b);
	B& operator=(const B& b);

private:
	int _b;
};
void Funtest()
{
	B b1;
	B b2(b1);
	b2 = b1;
}


编译结果:



所以说,一个类要防止被拷贝,要做两件工作

1>将拷贝构造函数和赋值运算符重载的访问权限设成私有的。

2>将拷贝构造函数和赋值运算符重载只给出声明而不对其进行定义。


(2)仿照上述思想,scopted_ptr的实现思想就是让单个对象独占空间,从而避免对象被多次释放的问题

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = NULL)
		:_p(ap)
	{
		cout<<"AutoPtr()"<<endl;
	}

	~AutoPtr()
	{
		if(NULL != _p)
		{
			cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
		}
	}
	T* Get()
	{
		return _p;
	}
	T* operator*()
	{
		return *_p;
	}
	T* operator->()
	{
		return _p;
	}
private:
	AutoPtr(const AutoPtr<T>& ap);
	AutoPtr<T>& operator=(const AutoPtr<T>& ap);
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p1 = new int;
	AutoPtr<int>* p2 = &p1;
	cout<<*(p2->Get)()<<endl;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}


运行结果:



注意:应避免使用出错,不要进行对象拷贝和赋值


4、unique_ptr(即scopted_array)

template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = NULL)
		:_p(ap)
	{
		cout<<"AutoPtr()"<<endl;
	}

	~AutoPtr()
	{
		if(NULL != _p)
		{
			cout<<"~AutoPtr()"<<endl;
			delete _p;
			_p = NULL;
		}
	}
	T& operator[](size_t index)
	{
		return _p[index];
	}
	const T& operator[](size_t index)const
	{
		return _p[index];
	}
	T* Get()
	{
		return _p;
	}
private:
	AutoPtr(const AutoPtr<T>& ap);
	AutoPtr<T>& operator=(const AutoPtr<T>& ap);
private:
	T* _p;
};
void Funtest()
{
	AutoPtr<int> p(new int[5]); 
	p[0] = 1;
	p[1] = 2;
	p[2] = 3;
	p[3] = 4;
	p[4] = 5;
	for(size_t i=0; i<5; i++)
	{
		cout<<p[i]<<" ";
	}
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}

运行结果:


注意:unique_ptr在boost库中是没有的,因为它就好比一个动态开辟的数组,可以用vector来实现,接口多,用着还方便,故有点多余。


5、引用计数版本的智能指针---->shared_ptr

     前面在string类中我们介绍过引用计数版本的各种情况分析,在这里我们就不啰嗦了,直接给出指针版本的。


有兴趣了解的请看:string类详解:

 http://blog.csdn.net/snow_5288/article/details/52901528




赋值运算符重载情况分类:


template <typename T>//引用计数版本
class SharedPtr
{
public:
	SharedPtr(T* sp = NULL)
		:_p(sp)
		,_pCount(NULL)
	{
		cout<<"SharedPtr()"<<endl;
		if(NULL != _p)
		{
			_pCount = new int(1);//仅当对象不为空时,才为其引用计数赋值
		}
	}
	
	SharedPtr(SharedPtr<T>& sp)//由于内部要改变ap的值,所以去掉const修饰
		:_p(sp._p)
		,_pCount(sp._pCount)
	{
		if(NULL != _p)//防止拷贝的对象本就是空
			++(*_pCount);
		sp._p = NULL;//让ap与动态申请的空间脱离关系
	}
	
	SharedPtr<T>& operator=(SharedPtr<T>& sp)
	{
		if(this != &sp)//
		{
			if(NULL == _pCount)//当前对象没有管理空间
			{
				_p = sp._p;
				_pCount = sp._pCount;
				if(sp._pCount)//原有空间管理了一段空间
					++(*_pCount);
			}
			else if(_pCount && 1==(*_pCount))//当前对象独占一块空间
			{
				delete _p;//释放当前对象
				delete _pCount;//释放当前对象
				
				_p = sp._p;
				_pCount = sp._pCount;
				if(sp._pCount)//原有空间管理了一段空间
					++(*_pCount);
			}
			else//当前对象和别人共享一段空间
			{
				--(*_pCount);
				
				_p = sp._p;
				_pCount = sp._pCount;
				if(sp._pCount)//原有空间管理了一段空间
					++(*_pCount);
			}
		}
		return *this;
	}
	
	~SharedPtr()
	{
		if(_pCount && 0==(--(*_pCount)))//注意释放条件
		{
			cout<<"~SharedPtr()"<<endl;
			delete _p;
			delete _pCount;//一定不要忘了释放
			_p = NULL;
			_pCount = NULL;
		}
	}
	T* operator*()
	{
		return *_p;
	}
	T* operator->()
	{
		return _p;
	}
private:
	T* _p;
	int* _pCount;
};

void FunTest()
{
	SharedPtr<int> sp1(NULL);
	SharedPtr<int> sp2(new int);
	SharedPtr<int> sp3(sp2);//sp2、sp3共享空间
	SharedPtr<int> sp4 = new int;//sp4独占空间
	SharedPtr<int> sp5(new int);
    sp4 = sp2;//当前对象独占空间
	sp1 = sp4;//当前对象sp1没有管理空间
	sp2 = sp5;
}
int main()
{
	FunTest();
	system("pause");
	return 0;
}

运行过程:


运行结果:

结论:shared_ptr可通过多个对象对申请的空间进行访问。

6>定置删除器(仿函数实现)
    
    上述5种实现智能指针的通病:由于文件指针用fopen打开之后必须拿对应的fclose函数将其关闭,故它们处理不了文件指针,所以就必须引出仿函数。

仿函数(functor):STL的六大组件之一,也叫函数对象。就是使一个类的使用看上去像一个函数。其实现原理就是就是在类中重载(),这个类就有了类似函数的行为,同时也成为一个仿函数类了。

用函数实现文件指针的关闭
#include<iostream>
#include<memory>
using namespace std;

void Fclose(FILE* pf)
{
	if(pf)
		fclose(pf);
}
int main()
{
	FILE* pf = fopen("1.txt","r");
	shared_ptr<FILE> sp(pf,Fclose);
	system("pause");
	return 0;
}

用仿函数实现
#include<iostream>
#include<memory>
using namespace std;

class Fclose// 定置的删除器仿函数
{
public:
	void operator()(FILE* pf)
	{
		if(NULL != pf)
			fclose(pf);
	}
};

int main()
{
	FILE* pf = fopen("1.txt","r");
	shared_ptr<FILE> sp(pf,Fclose());
	system("pause");
	return 0;
}

7>智能指针应用举例:实现多重冒泡排序
template <typename T>
class Great//按照升序冒泡
{
public:
	bool operator()(T& left,T& right)
	{
		return left>right;
	}
};
template <typename T>
class Less//按照降序冒泡
{
public:
	bool operator()(T& left,T& right)
	{
		return left<right;
	}
};
template <typename T,typename Compare>
void BubbleSort(T arr[],size_t size)
{
	size_t i,j;
	int flag = 0;
	for(i = 0; i<size-1; i++)
	{
		flag = 0;
		for(j = 0; j<size-1-i; j++)
		{
			if(Compare()(arr[j],arr[j+1]))//注意传参
			{
				flag = 1;
				swap(arr[j],arr[j+1]);
			}
		}
		if(flag == 0)
			break;
	}
}

int main()
{
	int arr[10] = {9,5,3,4,7,2,1,8,6,0};
	size_t size = sizeof(arr)/sizeof(arr[0]);
	BubbleSort<int,Less<int>>(arr,size);
	size_t idx = 0;
	cout<<"降序"<<endl;
	for(idx=0; idx<size; idx++)
	{
		cout<<arr[idx]<<" ";
	}
	cout<<endl<<endl;
	BubbleSort<int,Great<int>>(arr,size);
	cout<<"升序"<<endl;
	for(idx=0; idx<size; idx++)
	{
		cout<<arr[idx]<<" ";
	}
	system("pause");
	return 0;
}



运行结果:




8>智能指针<专指shared_ptr>的循环引用(非常重要)

先看一下什么是循环引用问题:

#include <iostream>
#include <memory>
using namespace std;

template <typename T>
class Node
{
public:
	Node(const T& value)
		:_pPre(NULL)
		,_pNext(NULL)
		,_value(value)
	{
		cout<<"Node()"<<endl;
	}
	~Node()
	{
		cout<<"~Node()"<<endl;
		cout<<"this:"<<this<<endl;
	}

	shared_ptr<Node<T>> _pPre;
	shared_ptr<Node<T>> _pNext;
	T _value;
};
void Funtest()
{
	shared_ptr<Node<int>> sp1(new Node<int>(1));
	shared_ptr<Node<int>> sp2(new Node<int>(2));

	cout<<"sp1.use_count:"<<sp1.use_count()<<endl;
	cout<<"sp2.use_count:"<<sp2.use_count()<<endl;

	sp1->_pNext = sp2;
	sp2->_pPre = sp1;

	cout<<"sp1.use_count:"<<sp1.use_count()<<endl;
	cout<<"sp2.use_count:"<<sp2.use_count()<<endl;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}
运行结果:



从运行结果中我们可以清楚地看到程序运行完成之后并没有释放我们的对象sp1和sp2,那么这是什么原因造成的呢?


从上面shared_ptr的实现中我们知道了只有当引用计数减减之后等于0,析构时才会释放对象,而上述情况造成了一个僵局,那就是析构对象时先析构sp2,可是由于sp2的空间sp1还在使用中,所以sp2.use_count减减之后为1,不释放,sp1也是相同的道理,由于sp1的空间sp2还在使用中,所以sp1.use_count减减之后为1,也不释放。sp1等着sp2先释放,sp2等着sp1先释放,二者互不相让,导致最终都没能释放,内存泄漏


解决办法:使用弱指针---->weak_ptr

#include <iostream>
#include <memory>
using namespace std;

template <typename T>
struct Node
{
public:
	Node(const T& value)
		:_value(value)
	{
		cout<<"Node()"<<endl;
	}
	~Node()
	{
		cout<<"~Node()"<<endl;
		cout<<"this:"<<this<<endl;
	}

	weak_ptr<Node<T>> _pPre;
	weak_ptr<Node<T>> _pNext;
	T _value;
};
void Funtest()
{
	shared_ptr<Node<int>> sp1(new Node<int>(1));
	shared_ptr<Node<int>> sp2(new Node<int>(2));

	cout<<"sp1.use_count:"<<sp1.use_count()<<endl;
	cout<<"sp2.use_count:"<<sp2.use_count()<<endl;
	
	sp1->_pNext = sp2;
	sp2->_pPre = sp1;

	cout<<"sp1.use_count:"<<sp1.use_count()<<endl;
	cout<<"sp2.use_count:"<<sp2.use_count()<<endl;
}
int main()
{
	Funtest();
	system("pause");
	return 0;
}
运行结果:


分析过程:


析构对象时,先析构sp2,可是由于sp2.use_count减减之后为0,释放sp2,sp1.use_count减减之后为0,释放sp1.

小细节:由于我们的引用计数也是智能指针,维护了一块空间,当第一次调用完析构函数时,sp2.weak_count--==1未能释放,此时sp1.weak_count也减减等于1,下一次调用析构函数时,sp1.weak_count--==0释放。


boost/shared_ptr的框架uml类图



  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值