智能指针是RAII思想的一种产品,那么什么是RAII呢?
RAII:Resource Acquistion Is Initialization
资源分配就初始化,定义一个类来封装资源的分配和释放。
在构造函数里完成资源分配初始化,在析构函数里完成资源的清理。
简单来举例:
template <class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
~AutoPtr()
{
if (_ptr)
{
delete _ptr;
}
}
private:
T* _ptr;
};
这个类就体现了RAII思想。
那么智能指针存在的意义是什么呢?c++用new/delete一样可以完成资源分配的初始化和清理。
问题就在于执行流的改变:
void Test()
{
int* p = new int(1);
if (p)
{
return;
}
delete p;
}
这段代码看似简单无误,其实已经存在了内存泄漏,因为return改变了执行流,导致无法释放。
改变执行流的语句还有很多:break,continue,gote,throw等,这些改变执行流的语句都可能导致已经new的空间无法delete,一旦代码非常多的时候,我们很难控制。所以使用智能指针是必要的!
首先,刚才写的AutoPtr这个类既然叫智能指针,那么它就要像个指针,我们有必要实现它的operator*和operator->。
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
(AA就是一个用来举例的,类有两个整形的成员变量)
要像个指针还要能赋值,但是当我赋值时: AutoPtr ap3(ap);
它崩溃了,原因很好理解,浅拷贝导致的两个对象的指向同一片空间,那么析构的时候这片空间必然会析构两次。还有一个问题,就是一个的改变会影响另一个。这些是我们不希望看到的,所以c++专家们在c++98给出了解决方案:auto_ptr
它的核心思想就是管理权转移,当一个指针把值赋给另一个时,它马上赋值为NULL。
以下是它的简单模拟实现:
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)
{
if (_ptr)
{
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~AutoPtr()
{
if (_ptr)
{
delete _ptr;
}
}
private:
T* _ptr;
};
这样做就不会出问题,operator=同理。
但是这样做有很大的缺陷,ap = NULL;那么以后就不能使用它,很容易出错。
现在它已经被禁用。
在c++98提出auto_ptr后,由于有很大缺陷,有人忍的了继续使用,有人忍不了,所以非官方boost总结了一套方案它们分别是:
scoped_ptr shared_ptr weak_ptr
我来分别介绍:
1.scoped_ptr:防拷贝,简单粗暴
template <class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~ScopedPtr()
{
if (_ptr)
{
delete _ptr;
}
}
private:
//1.定为私有 2.只声明不定义
ScopedPtr(ScopedPtr<T>& sp);
ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
private:
T* _ptr;
};
直接把拷贝构造函数和operator=定义为私有,而且只声明,那么不能赋值就不会出错,这是个简单粗暴的办法!
2.shared_ptr:用了引用计数的方法
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _refCount(new int(1))
{}
SharedPtr(SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _refCount(sp._refCount)
{
++(*_refCount);
}
SharedPtr<T>& operator=(SharedPtr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_refCount) == 0)
{
delete _ptr;
delete _refCount;
}
_ptr = sp._ptr;
_refCount = sp._refCount;
++(*_refCount);
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~SharedPtr()
{
if (--(*_refCount) == 0)
{
delete _ptr;
delete _refCount;
}
}
private:
T* _ptr;
int* _refCount;
};
可以看出,_refCount用来计数,接下来分析它的赋值:
拷贝构造:
operator=:
它有不同的情况:
①自己给自己赋值:sp1 = sp1;或者sp2 = sp1;(sp2已经等于sp1)
②如果被赋值对象的*_refCount == 1,那么直接释放它,如果被赋值对象的*_refCount > 1,那就把它的_refCount减1.
然后析构函数根据*_refCount来决定是否清理对象,就不会存在浅拷贝的问题。
3.weak_ptr是用来解决shared_ptr的循环引用的问题,我在《c++:分析智能指针shared_ptr存在的循环引用的缺陷》一文会详细分析。
接下来在c++11,官方延用了boost总结的方法,需要注意的是,c++11把scoped_ptr改名为unique_ptr,并且没有写数组。
智能指针发展历史的总结:
boost和c++11有一些重名的智能指针,但是不影响使用,因为他们分别在boost::和std::