发布时间:2022-10-14 20:30
目录
1 基本概念
2 auto_ptr
3 unique_ptr
5 weak_ptr
智能指针是针对资源因捕捉异常跳过资源释放(如下图)、或者开辟了空间没有释放等情况所提出的。其解决的办法就是使用RAII(Resource Acquisition Is Initialization)技术利用对象生命周期来控制程序资源。做法就是把普通的指针封装到类中,在析构函数中进行资源释放。这样就不用显示进行资源释放,只要一个智能指针对象生命周期结束,就会自导调用析构函数释放资源。
void func()
{
int* pi = new int;
if(pi == nullptr)
throw 1;
delete pi;
}
int main()
{
try
{
func();
}
catch
{
cout<
class smart_ptr
{
public:
smart_ptr(T* ptr)
:_ptr(ptr)
{}
~smart_ptr()
{
if (_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
上述的智能指针中可以自动释放资源了,但是有一个问题是在拷贝构造上。使用拷贝构造会使两个指针指向同一个空间,这样没有问题与原生指针一样。但当析构时同一片空间就会释放两次导致错误。为解决这个问题,又有了以下这些智能指针。
auto_ptr是C++98的语法,其拷贝构造不是完全的拷贝,而是进行资源转移,将拷贝过来的原指针置空。
template
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(const auto_ptr& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
~auto_ptr()
{
if(_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
这样的做法虽然解决了两次析构,但原指针无法使用与原生指针的情况不同,这并不是我们想看到的。因而auto_ptr没有太大的用途,基本上不使用。后来boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr。C++ 11参考boost中的实现的,引入了unique_ptr和shared_ptr和weak_ptr。unique_ptr对应boost的scoped_ptr。
unique_ptr也是简单粗暴的将拷贝构造和拷贝赋值直接删除。一个空间只能由一个unique_ptr指向。
template
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
unique_ptr(const unique_ptr& up) = delete;
unique_ptr& operator=(const unique_ptr& up) = delete;
~unique_ptr()
{
if (_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
shared_ptr采用引用计数的方式,终于能使用多指针指向一个空间。在引用计数上的增加减少上要使用锁来保证线程安全。
template
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
,_pRefCount(new int(1))
,_pmtx(new mutex)
{}
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
,_pRefCount(sp._pRefCount)
,_pmtx(sp._pmtx)
{
AddRef();
}
shared_ptr& operator=(const shared_ptr& sp)
{
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pmtx = sp._pmtx;
AddRef();
}
return *this;
}
~shared_ptr()
{
Release();
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
void Release()
{
_pmtx->lock();
bool flag = false;
if (--(*(_pRefCount)) == 0 && _ptr)
{
delete _ptr;
delete _pRefCount;
flag = true;
}
_pmtx->unlock();
if (flag)
delete _pmtx;
}
void AddRef()
{
_pmtx->lock();
++(*_pRefCount);
_pmtx->unlock();
}
private:
T* _ptr;
int* _pRefCount;
mutex* _pmtx;
};
shared_ptr已经能解决大部分的场景了,但是在循环引用下就会出问题。例如下图在一个双链表中,A、B、next、prev都使用shared_ptr,A与prev指向一个节点,引用计数为2。B与next也指向一个节点。A、B指针指向节点释放,各自引用计数减1。A中next指向的节点要释放,则B指针指向节点要释放。B指针指向节点要释放,则B中prev指向的节点要先释放,该节点要释放又要释放next指向节点。这样就导致了循环引用问题,两个节点都无法释放。
weak_ptr就是为了解决shared_ptr循环引用的。它在拷贝构造和拷贝赋值时的引用计数不增加。注意它不能以原生指针构造,只适用于在循环引用场景将shared_ptr构造为weak_ptr。
template
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
{}
weak_ptr& operator=(const shared_ptr& sp)
{
_ptr = sp._ptr;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pRefCount;
};