智能指针?是一个指针吗?这里给大家说的是,它不是一个指针,但它模拟了指针所具有的功能。那么,为什么要有智能指针的引入呢?看看下面的例子吧~
void FunTest()
{int *p = new int[10];FILE *pFile = fopen("1.txt","r");if(pFile == NULL){return;}if(p){delete[] p;p = NULL;}
}
在c++动态内存分配空间时,是由用户自己维护的,所以当new出来空间后,必须由用户手动释放。再看看上面的这个代码,有什么问题呢?显然的,当1.txt这个文件不存在时,pFile就为空,这就使后面释放p的语句没有执行,导致内存泄漏。那么怎么解决这类问题呢?
这就引入了我们重要的智能指针,所谓智能指针就是智能化的管理动态开辟的资源的释放工作。
1、模拟实现auto_ptr
template<typename T>
class AutoPtr
{
public:AutoPtr(T* p):_p(new T(1)){cout<<"AutoPtr()"<<endl;_p = p;}AutoPtr(AutoPtr& ap):_p(ap._p){cout<<"AutoPtr(AutoPtr& ap)"<<endl;ap._p = NULL;}AutoPtr<T>& operator=(AutoPtr& ap){cout<<"AutoPtr<T>& operator=(AutoPtr& ap)"<<endl;if(this != &ap){delete _p;_a = ap._p;ap._p = NULL;}}~AutoPtr(){cout<<"~AutoPtr()"<<endl;if (_p){delete _p;_p = NULL;}}
public:T& operator*(){return *_p;}T* operator->(){return _p;}
private:T *_p;
};void FunTest()
{AutoPtr<int> ap = new int;AutoPtr<int> ap1(ap);*ap1 = 10;*(ap1.operator->()) = 20;
}
int main()
{FunTest();return 0;
}
智能指针AutoPtr的特点是:只可以管理一个对象,看一下监视窗口:
这样就会导致资源被转移。
当然,AutoPtr还有另一种实现方式:
template<typename T>
class AutoPtr
{
public:AutoPtr(T* p):_p(new T(1)){cout<<"AutoPtr()"<<endl;_p = p;_owner = true;}AutoPtr(AutoPtr& ap):_p(ap._p){cout<<"AutoPtr(AutoPtr& ap)"<<endl;ap._owner = false;ap._p = NULL;_owner = true;}AutoPtr<T>& operator=(AutoPtr& ap){cout<<"AutoPtr<T>& operator=(AutoPtr& ap)"<<endl;if(this != &ap){delete _p;_a = ap._p;ap._owner = false;ap._p = NULL;_owner = true;}}~AutoPtr(){cout<<"~AutoPtr()"<<endl;if (_p){delete _p;_p = NULL;_owner = false;}}
public:T& operator*(){return *_p;}T* operator->(){return _p;}
private:T *_p;bool _owner;
};void FunTest()
{AutoPtr<int> ap = new int;AutoPtr<int> ap1(ap);*ap1 = 10;*(ap1.operator->()) = 20;}int main()
{FunTest();return 0;
}
这种方式就是定义一个私有成员,用来标记当前对象的状态,若为false,表示当前对象已不再指向任何空间,若为true,说明该对象指向申请的那块内存。
那么,这种类型的指针有什么弊端吗?首先,只能管理单个对象,每次只有一个对象可使用申请的空间。其次,使得资源转移,另一个对象使用时,前一个对象将赋为空。
但是切记不要使用auto_ptr。
2、模拟实现scoped_ptr
scoped_ptr与auto_ptr一样,都是管理单个对象的。它的作用是:在一个类中防止拷贝。说起防止拷贝,大家会想到把它定义为私有的,总可以了吧。其实并不可以,因为访问私有成员或函数的方式还有将调用它的函数声明为类的友元就ok啦。所以,在这里,我们将学到一种防拷贝的方式:只声明不定义,且将拷贝构造和赋值运算符重载声明为私有即可。
实现如下:
template<typename T>
class ScopedPtr
{
public:ScopedPtr(const T* p):_p(p){}~ScopedPtr(){if(_p){delete _p;_p = NULL;}}T& operator*(){return *_p;}T* operator->(){return _p;}
private:ScopedPtr(const ScopedPtr&);ScopedPtr<T>& operator=(const ScopedPtr&);
private:T* _p;
};void FunTest()
{ScopedPtr<int> sp = new int;ScopedPtr<int> sp1(sp); //error,这样就使用不了拷贝构造函数了ScopedPtr<int> sp1 = sp; //error,使用不了赋值运算符重载}int main()
{FunTest();return 0;
}
scoped_ptr实现的机制体现了它的独占性,当一个对象占用一块空间时,其他对象将无法使用。并且解决了不让资源转移的问题。
注:在STL源码库中使用的是scoped_ptr,而在boost库中,使用的是unique_ptr。两个其实是一样的。
3、模拟实现shared_ptr
前面两个智能指针都是管理单个对象,而这个shared_ptr则是可以管理多个对象。它们之间的资源是共享的。
以下代码是使用引用计数的方式实现资源共享。
实现代码如下:
template<typename T>
class SharedPtr
{
public:SharedPtr(T* p = NULL):_p(p),_pCount(new int(1)){cout<<"SharedPtr(T* p = NULL)"<<endl;}SharedPtr(SharedPtr& sp):_p(sp._p),_pCount(sp._pCount){cout<<"SharedPtr(SharedPtr& sp)"<<endl;}SharedPtr<T>& operator=(const SharedPtr& sp){cout<<"SharedPtr<T>& operator=(const SharedPtr& sp)"<<endl;if(_p != sp._p){if(_p == NULL){_p = sp._p;_pCount = sp._pCount;}else if(_p && (*_pCount== 1)){delete _p;_p = sp._p;_pCount = sp._p;}else{--(*_pCount);_p = sp._p;_pCount = sp._pCount;}if(sp._p)++(*_pCount);}return *this;}~SharedPtr(){cout<<"~SharedPtr()"<<endl;if(_p && --(*_pCount) == 0){delete _p;_p = NULL;}}
public:T& operator*(){return *_p;}T* operator->(){return _p;}int UseCount(){if(_pCount != NULL)return *_pCount;return 0;}private:T *_p;int *_pCount;
};void FunTest()
{SharedPtr<int> sp = new int(1);SharedPtr<int> sp1(sp);SharedPtr<int> sp2;sp = sp1;sp2 = sp1;cout<<sp.UseCount()<<endl;cout<<sp1.UseCount()<<endl;cout<<sp2.UseCount()<<endl;
}int main()
{FunTest();return 0;
}
运行结果:
此版本虽然实现了共享,但也存在着不少问题,比如线程安全问题、循环引用问题,定置删除器问题等。这几个问题我们下一篇再详细解说哦。
希望大家可以对博客提出宝贵意见,欢迎来访哦。