为什么引入智能指针
我们知道当我们定义一个普通指针变量的时候,我们在最后的时候,要把这个指针给销毁,以防资源泄露。 但是当创建指针和销毁指针期间如果发生了异常,这时就会发生资源泄露,由此智能指针应运而生,它可以自动销毁,避免了资源泄露的问题
auto_ptr
#include<iostream>
using namespace std;
namespace zy {
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = NULL)
:_ptr(ptr)
{
\
}
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
\
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (ap != &this)
\
{
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
delete _ptr;
}
T& operator*() { return *_ptr; }
T* operator->(){return _ptr;
}
private:
T* _ptr;
\
};
}
class Date
{
public:
Date() { cout << "Date()" << endl; }
~Date() { cout << "~Date()" << endl; }
int _year;
int _month;
int _day;
};
int main()
{
zy::auto_ptr<Date> ap1(new Date);
zy::auto_ptr<Date> ap2(ap1);
ap1->_day = 0;
\
\
}
缺点:这个智能指针当A指针赋值给另一个B指针的时候,A指针会发生悬空,导致A指针无法正常使用
unque_ptr
template<class T>
class unique_ptr
{
unique_ptr(T* ptr = NULL)
:_ptr(ptr)
{
}
unique_ptr(unique_ptr<T>& ap)
:_ptr(ap._ptr)
{
}
unique_ptr<T>& operator=(unique_ptr<T>& ap)
{
if (ap != &this)
{
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~unique_ptr()
{
if (_ptr)
delete _ptr;
}
T& operator*() { return *_ptr; }
T* operator->() {
return _ptr;
}
private:
unique_ptr(unique_ptr<T>& ap)
:_ptr(ap._ptr)
{
}
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
T* _ptr;
};
缺点:这个智能指针会简单粗暴的直接不让拷贝构造和赋值,所以多个对象无法共享同一份资源
shared_ptr
struct Date
{
int _year = 0;
int _month = 0;
int _day = 0;
};
namespace wzy {
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
, _pRefCount(new int(1))
,_pmtx(new mutex)
{
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
,_pmtx(sp._pmtx)
{
++(*_pRefCount);
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (this->_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pmtx = sp._pmtx;
AddRef();
}
return *this;
}
~shared_ptr()
{
if (--(*_pRefCount) == 0 && _ptr)
{
cout << "delete" << _ptr << endl;
delete _ptr;
delete _pRefCount;
_ptr = nullptr;
_pRefCount = nullptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
void Release()
{
_pmtx->lock();
bool flag = false;
if (--(*_pRefCount) == 0)
{
delete _ptr;
delete _pRefCount;
flag = true;
}
_pmtx->unlock();
if(flag)
delete _pmtx;
}
void AddRef()
{
_pmtx->lock();
++(*_pRefCount);
_pmtx->unlock();
}
int useCount()
{
return *_pRefCount;
}
private:
T* _ptr;
//static int _refCount = 0;
int* _pRefCount;
mutex* _pmtx;//只有用指针才能
};
}
shared_ptr采用了引用计数的方式,通过计数来记录构造的次数,
-
shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
-
在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
-
如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
-
如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。进而完成合理的析构,但是缺点是会产生循环引用
std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);
n1->_next = n2;
n2->_prev = n1;
上面这个代码就会产生循环引用的问题,这两个指针的析构会互相依赖,n2析构要依赖n1,n1析构要依赖n2,这样就产生了一个死循环了,就会发生错误。这样我们就引入了weak_ptr
weak_ptr
我常常把weak_ptr当作shared_ptr的小弟,我们知道shared_ptr不仅可以访问指向的内存区域而且还可以掌管指向区域的“生死大权”,也就是说伴随着shared_ptr指针生命周期的结束,指针指向的区域也会被释放掉。但是weak_ptr作为shared_ptr的小弟,weak_ptr不敢这样做,作为智能指针中最弱的一个,weak_ptr只能访问所指向的内存区域,当weak_ptr指针生命结束之时,其所指向的内存依旧完好无损,这就是weak_ptr小弟和shared_ptr大哥的本质区别。
由于在weak_ptr指针生命结束之时,不会对指向内存产生任何影响,因此不会出现“上述shared_ptr引发的环形引用的异常错误”。如果将上述例子中,shared_ptr换做weak_ptr结构将会发生变化:
也就是说,这里的shared_ptr变成weak_ptr就会解决相应的问题了。