什么是智能指针
智能指针就是一个类,这个类中的构造函数中传入一个指针,析构函数中释放这个指针,具有和指针相同的功能。智能指针都是栈上的对象,当函数或程序结束时会自动释放。
为什么需要智能指针
我们知道在C++由于没有内存回收机制,因此每次申请的资源都需要及的手动释放掉,但是有时候由于程序员的疏忽或者是程序出错导致未能购释放掉资源,这就造成了内存泄漏。这也是很让人烦恼的,所以出现智能指针来解决这个问题!!!!
智能指针的原理
RAII:获取资源即是初始化,是一种利用对象生命周期来控制资源的技术,在对象构造是获取资源,在对象析构时释放资源,在对象的整个生命周期中控制资源使其在整个生命周期中始终有效;其实就是将管理一个资源的任务交给一个对象;
智能指针底层就是:RAII + 像指针一样去使用(重载*/->)
智能指针的分类
- C++98中 auto_ptr(管理权转移)
template<class T>
class Auto_ptr
{
public:
Auto_ptr(T* ptr)
:_ptr(ptr)
{
cout << "Auto_ptr" << endl;
}
Auto_ptr(Auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
cout << "Auto_ptr(Auto_ptr<T>& ap)" << endl;
ap._ptr = nullptr;
}
Auto_ptr<T>& operator=(Auto_ptr<T>& ap)
{
cout << "Auto_ptr<T>& operator=(Auto_ptr<T>& ap)" << endl;
if (_ptr)
delete _ptr;
if (this != &ap)
{
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~Auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
当然这种最早出现的智能指针,是却在严重问题的;这个问题就是会造成----指针悬空,因为auto_ptr使用的是管理权转移的方式,当该智能指针发生拷贝或赋值时,会将资源管理权移交给新的指针,而原指针的就会被悬空,这样很容易导致当对原指针解引用和析构时造成发生错误以及造成不必要的开销;所以auto_ptr已经在C++中被弃用了;在日常使用中避免使用auto_ptr;
- C++11中 unique_ptr(独享资源-禁止拷贝和赋值)
template<class T>
class Unique_ptr
{
public:
Unique_ptr(T* ptr)
:_ptr(ptr)
{
}
~Unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
//C++11 防止拷贝的方法 delete
Unique_ptr(Unique_ptr<T>& up) = delete;
Unique_ptr<T>& operator =(Unique_ptr<T>& up) = delete;
//C++98 防止拷贝的方法 只声明不定义 + 声明为private
Unique_ptr(Unique_ptr<T>& up)
{}
Unique_ptr<T>& operator =(Unique_ptr<T>& up)
{}
private:
T* _ptr;
};
unique_ptr人如其名,唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象,避免了auto_ptr的问题,这样无疑是提高了智能指针的安全性,所以当我们的资源不需要拷贝共享的时候,我们就使用unique_ptr这样是最安全的;
- C++11中 share_ptr(采用引用计数允许多个指针指向相同对象)
template<class T>
class Share_ptr
{
public:
Share_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _RefCount(new int(1))
, _pMutex(new mutex)
{
}
~Share_ptr()
{
ReleaseRef();
}
Share_ptr(const Share_ptr<T>& sp)
:_ptr(sp._ptr)
, _RefCount(sp._RefCount)
, _pMutex(sp._pMutex)
{
AddRef();
}
Share_ptr<T>& operator=(const Share_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
ReleaseRef();
_ptr = sp._ptr;
_RefCount = sp._RefCount;
_pMutex = sp._pMutex;
AddRef();
}
return *this;
}
void AddRef()
{
_pMutex->lock();
++(*_RefCount);
_pMutex->unlock();
}
void ReleaseRef()
{
bool deleteflag = false;
_pMutex->lock();
if (--(*_RefCount) == 0 && _ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
delete _RefCount;
deleteflag = true;
}
_pMutex->unlock();
if (deleteflag == true)
{
delete _pMutex;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* GetPtr() const
{
return _ptr;
}
private:
T* _ptr;
int* _RefCount;
mutex* _pMutex;
};
share_ptr允许多个指针指向同一个对象,采用引用计数的方式,每一个的拷贝和赋值时,对象的引用计数+1,当指向该对象的指针析构时,对象的引用计数-1,当对象的引用计数为1是说明此时只有一个指针指向该对象,当该指针析构时对象也会随之被释放;否则其他时候对象时不被释放的,shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。;
循环引用问题
这是share_ptr所带来的的问题,当然这种问题的出现也是只有某些特殊场景下才会出现;设想当share_ptr去管理一个双向链表时:
这就会造成一种现象,node1所管理的对象同时也被node2中的_prev管理着,node2所管理的对象同时也被node1中的_next所管理着;所以这两个对象的引用计数都成立2,但是当出了函数作用域时,node1和node2进行析构了,引用计数减1,此时两个空间的引用计数变成了1,所以这个资源没有被释放,而是相互牵制对方,这样无疑造成了内存泄漏;但是针对这样的问题,C++11中也响应给出了解决方案:那就是引入weak_ptr(弱智能指针)
- C++11中 weak_ptr(弱智能指针配合share_ptr解决循环引用问题)
注意:
weak_ptr是唯一一个没有利用RALL思想的智能指针
weak_ptr可以想指针一样去使用,但是不会增加引用计数,可以访问管理的资源,但是不参与资源的释放
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.GetPtr())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.GetPtr();
return *this;
}
~weak_ptr()
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
这样就能很好的解决share_ptr的循环引用问题;
C++11和boost库关系
众所周知,C++的更新速度是特别慢的,C++98中出现的auto_ptr采用了一种很简单的实现,带来的很多问题,但是C++并没有及时的更新,所以这是民间大神就写出来boost库,此时boost库中出现scoped_ptr、scoped_array/share_ptr这些智能指针来代替auto_ptr,并且有着很好地效果,知道C++11的出现,C++官方更新了智能指针便是借鉴了许多boost库中的动析,这才用了unique_ptr、share_ptr;