我之前我们谈异常的时候,异常有异常安全的问题。
详细请看之前的异常
https://blog.csdn.net/Amour_Old_flame/article/details/86759959
我们提出了RAII的解决方案,那么什么是RAII呢?
是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的
时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这样做有两个优势:
1.不需要我们显示的释放资源
2.用这种方式,对象所需要的资源在其生命周期时钟有效。
template
class SmartPtr
{
public:
SmartPtr(T* ptr == nullptr) :_ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
这就是RAII,但是他并不能称为智能指针。因为他不能像指针一样使用,所以我们还要对 * -> 进行重载。
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
这样才能称为一个简单的智能指针。
当然在我们的C++库中也提供了一些智能指针。、
C++98的库中提供的一个智能指针,他最大的特点就是他的拷贝构造
AutoPtr(AutoPtr& ap)
: _ptr(ap._ptr)
{
ap._ptr = NULL; //赋值完之后前面的对象置空
}
一旦发生拷贝,就将ap的资源转移到当前对象中,然后将ap与资源的联系断开
这样做会导致拷贝后ap为空,使得ap对象悬空了,如果再次通过ap对象访问资源就会导致程序崩溃。
auto_ptr主要采用管理权转移,拷贝时会使对象悬空,设计有缺陷。
C++11中提供了unique_ptr
他的做法是将拷贝构造私有,不支持拷贝
private:
// C++98防拷贝的方式:只声明不实现+声明成私有
UniquePtr(UniquePtr const &);
UniquePtr & operator=(UniquePtr const &);
// C++11防拷贝的方式:delete
UniquePtr(UniquePtr const &) = delete;
UniquePtr & operator=(UniquePtr const &) = delete;
因为auto_ptr的拷贝有大的缺陷,所以unique_ptr粗暴的不能拷贝,但是却没有明显的缺陷。比auto_ptr更适合使用
shared_ptr也是C++11提供的,这个智能指针就比较强大,没有大的缺陷,并且支持拷贝。
shared_ptr的原理主要是:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。就是有一个计数,当计数减为0的时候再去释放资源,这样就可以防止资源被多次释放。
主要有四点:
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
下面来简单的实现一下shared_ptr
template
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL):_ptr(ptr),_pRefCount(new int(1)),_pMutex(new std::mutex)
{
if(_ptr == NULL)
{
_pRefCount = 0;
}
}
SharedPtr(SharedPtr& sp):_ptr(sp._ptr),_pRefCount(sp._pRefCount),_pMutex(sp._pMutex)
{
if(_ptr)
{
AddRefCount();
}
}
SharedPtr& operator=(SharedPtr& sp)
{
if(_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
if(_ptr)
{
AddRefCount();
}
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int UseCount()
{
return *_pRefCount;
}
T* Get()
{
return _ptr;
}
int AddRefCount()
{
_pMutex->lock();
(*_pRefCount)++;
_pMutex->unlock();
return *_pRefCount;
}
int SubRefCount()
{
_pMutex->lock();
(*_pRefCount)--;
_pMutex->unlock();
return *_pRefCount;
}
~SharedPtr()
{
Release();
}
private:
void Release()
{
if(_ptr && SubRefCount() == 0)
{
delete _ptr;
delete _pRefCount;
}
}
private:
T* _ptr;
int* _pRefCount;
std::mutex* _pMutex;
};
虽然shared_ptr看起来完美无缺,但是其实他还是有一个小的缺陷,那就是在特定场景下会发生循环引用的问题。
下面详细讲解一下
我们有两个几点node1 和 node2 分别有两个智能指针管理,他们的引用计数都变成了1.
node1->next = node2;
node2->prev = node1;
这样他们的引用计数都变成了2.
node1和node2析构,引用计数又减到1,但是node1->next 仍然指向node2,同时node2->prev指向node1。
此时当next和prev析构,node1和node2才会释放掉。
但是问题出现了next属于node1的成员,node1释放了,next才会析构,而node1由prev管理,prev属于node2成员,所以这就叫循环引用,谁也不会释放。都等着对方先放手,但谁也不会放手。这就是循环引用的问题。
那如何解决呢?
这时候就出现了shared_ptr的小保姆weak_ptr。 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
weak_ptr没有引用计数,析构也不会释放资源不是RAII,他是专门用于解决shared_pre出现的循环引用问题。