RAII(Resource Acquisition Is Initialization)

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

所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。

STL--auto_ptr

Boost库的智能指针:scoped_ptr/scoped_array/shared_ptr/shared_array/weak_ptr/intrusive_ptr.

(ps:新的C++11标准中智能指针:模拟实现auto_ptr,scoped_ptr,shared_ptr已经引入了unique_ptr/shared_ptr/weak_ptr)

常见的智能指针有:auto_ptr/scoped_ptr/scoped_array/shared_ptr/shared_array。由于scoped_array和scoped_ptr比较类似,shared_array和shared_ptr又比较类似,所以我们只实现auto_ptr/scoped_ptr/shared_ptr。

下面介绍三种智能指针auto_ptr,scoped_ptr,shared_ptr。

智能指针的基本特点

        1)智能指针管理的是一块内存的释放。

        2)智能指针是一个类,有类似指针的功能。

1、AutoPtr

    首先auto_ptr的成员变量主要有T* _ptr,bool _owner时,主要实现原理是在构造对象时赋予其管理空间的所有权,在析构函数中通过_owner的真否来释放所有权,并且在拷贝或赋值后通过将_owner设为false,转移空间的所有权。但是此做法存在一些问题,主要问题是如果拷贝出来的对象比原来的对象出作用域,则原来的对象的_owner虽然为false,但会访问一块已经释放的一块空间。

   改进后还是管理空间的所有权转移,但这种实现方法中没有_owner。构造和析构和上述实现方法类似,但拷贝和赋值后直接将_ptr赋为空,禁止其在访问原来的内存空间。

//智能指针AutoPtr
template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
	{}
	AutoPtr(AutoPtr<T> &ap)
		:_ptr(ap._ptr)
	{//拷贝后将原有指向同一块内存的指针置空
		ap._ptr = NULL;
	}
	AutoPtr<T>& operator=(AutoPtr<T> &ap)
	{
		if (this != &ap)
		{
			delete _ptr;
			_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}
		T& operator*()
	{
		return *_ptr;
	}
	T* GetPtr()//返回原指针_ptr
	{
		return  _ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~AutoPtr()
	{
		cout << "~AutoPtr()" << endl;
		if (_ptr)
		{
			delete _ptr;
			_ptr = NULL;
		}
	}
private:
	T* _ptr;
};

2、ScopedPtr

    因为智能指针容易出现拷贝时释放两次的情况,所以ScopedPtr主要是进行防止拷贝,防止拷贝的两条必须要满足的条件是:

1)设置保护限定符;2)对拷贝构造函数和赋值运算符重载进行之声明不定义。

如没有设置保护限定符,若在类外进行定义后,则会出现问题,所以说这两个条件是必不可少的。这样就能够避免上面所出现的问题,但是这样就造成了它在功能上的缺陷。

//智能指针ScopedPtr
template<class T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr)
	    :_ptr(ptr)
	{}
	T& operator*()
	{
		return *_ptr;
	}
	T* GetPtr()//返回原指针_ptr
	{
		return  _ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~ScopedPtr()
	{
		cout << "~ScopedPtr()" << endl;
		if (_ptr)
		{
			delete _ptr;
			_ptr = NULL;
		}
	}
protected://防拷贝:此处为privated或protecte;拷贝构造和赋值函数只声明不定义
	ScopedPtr(const ScopedPtr<T> &sp);
	ScopedPtr<T>& operator==(const ScopedPtr<T> &sp);
private:
	T* _ptr;
};

3、SharedPtr

    SharedPtr指主要的原理是利用引用计数的浅拷贝来实现,通过多开辟4个字节的方式,存储引用计数,当有指针指向这块空间时,引用计数+1。如若析构时,先将这块空间的引用计数降为1,然后在进行析构,避免了析构多次的问题。

//智能指针SharedPtr
template<class T>
class SharedPtr
{
public:
	SharedPtr()
		:_ptr(NULL)
		, _pCount(new long(1))
	{}
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pCount(new long(1))
	{}
	//----引用计数浅拷贝
	SharedPtr(const SharedPtr<T> &sp)
		:_ptr(sp._ptr)
		, _pCount(sp._pCount)
	{
		++(*_pCount);
	}
	SharedPtr<T>& operator=(const SharedPtr<T> &sp)
	{
		if (this != &sp)
		{
			Release();
			_ptr = sp._ptr;
			_pCount = sp._pCount;
			++(*_pCount);
		}
		return *this;
	}
	////用深拷贝
	////传统写法
	//SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	//{
	//	if (this != &sp)
	//	{
	//		SharedPtr<T> tmp(sp);
	//		swap(_ptr, tmp._ptr);
	//		swap(_pCount, tmp._pCount);
	//	}
	//	return *this;
	//}
	//现代写法
	//SharedPtr<T>& operator=(SharedPtr<T> sp)
	//{
	//	swap(_ptr, sp._ptr);
	//	swap(_pCount, sp._pCount);
	//	return *this;
	//}
	~SharedPtr()
	{
		cout << "~SharedPtr()" << endl;
		Release();
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* GetPtr()//返回原指针_ptr
	{
		return  _ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	long GetCount()//返回同一内存的指向数
	{
		return *_pCount;
	}
	void Release()//释放内存
	{
		if (--(*_pCount) == 0)
		{
			delete _ptr;
			delete _pCount;
			_ptr = NULL;
			_pCount = NULL;
		}
	}
private:
	T* _ptr;
	long* _pCount;
};

测试用例如下:

void  Test()
{
	AutoPtr<int> ap1(new int(3));
	AutoPtr<int> ap2 = ap1;
	AutoPtr<int> ap3(new int(5));
	ap3 = ap2;

	ScopedPtr<int> sp1(new int(3));
	//防拷贝
	//ScopedPtr<int> sp2 = sp1;
	//sp2 = sp1;

	SharedPtr<int> shp1(new int(3));
	SharedPtr<int> shp2 = shp1;
	SharedPtr<int> shp3(new int(5));
	shp3 = shp1;
	*shp1 = 6;
	cout << &shp1 << endl;
	cout << shp1.GetCount() << endl;
	cout << *(shp1.GetPtr()) << endl;//输出_ptr的值
	cout << *(shp1.operator->()) << endl;//输出_ptr的值
	
	SharedPtr<Node> shpNode(new Node);
	shpNode->_data = 10;
	cout << shpNode->_data << endl;
}

struct Node
{
	int _data;
	Node* _next;
};

然而上面用引用计数实现的简化版看起来不错,但却存在一些问题,具体关注本人博客。

【小知识】模板的分离编译

1、在模板头文件 xxx.h 里面显示实例化->模板类的定义后面添加 template class SeqList<int >; 一般不推荐这种方法,一方面老编译器可能不支持,另一方面实例化依赖调用者。(不推荐)

2、将声明和定义放到一个文件 "xxx.hpp" 里面,推荐使用这种方法。

以上是本人对智能指针的一些认识,如有不足,请多指教。