1.为什么需要智能指针?
当我们在代码中申请资源时,方式有多种,malloc出来的就需要free掉, new申请的就需要delete来释放。在这过程中,很容易发生申请资源后忘记释放的情况,这就会导致内存泄漏。不仅如此,如果在malloc和free之间如果存在抛异常,那么还是有内存泄漏。这种问题就叫异常安全。
而智能指针的存在就可以完美的帮助我们自动释放对象的资源,避免内存的泄露,确保安全。
2.什么是智能指针?
智能指针是C++中预防内存泄漏的方式:
就是让一个类管理原生态指针,自动调用析构函数,用户不需要关心资源何时去释放。
智能指针原理:RAII+类似指针的行为(重载operator()和operator->())+解决浅拷贝
3.智能指针版本
C++98:
auto_ptr(1.0)
RAII+类似指针的行为+解决浅拷贝(资源的转移)
缺陷:多个对象不能同时访问同一份资源
namespace bia
{
template <class T>
class auto_ptr
{
public:
//RAII
auto_ptr(T* ptr)
:_ptr(ptr)
{}
//ap2(ap1);
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
//ap1=ap2
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
}
}
//让类对象具有指针类似的行为
T& operator*()
{
return *_ptr;
}
T& operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
}
void TestFunc()
{
//常规指针
int a = 10;
int* p1 = &a;
int* p2(p1);
*p1 = 20;
*p2 = 30;
bia::auto_ptr<int> ap1(new int);
//解决方式:将资源转移
bia::auto_ptr<int> ap2(ap1);
//*ap1 = 10;//会崩溃---ap1已经将资源转移给了ap2,自己与资源断开链接
*ap2 = 20;
bia::auto_ptr<int> ap3(new int);
ap3 = ap2;
}
auto_ptr(2.0)
RAII+类似指针的行为+解决浅拷贝(资源管理权的转移)
缺陷:可能会产生野指针
namespace bia
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
:_ptr(_ptr)
,_owner(false)
{
if (_ptr)
_owner = true;
}
auto_ptr(const auto_ptr<T>& ap)
: _ptr(ap._ptr)
, _owner(ap._owner)
{
ap._owner = false;
}
auto_ptr<T>& operator=(const auto_ptr<T>& ap)
{
if (this != &ap)
{
//先处理以前的旧资源
if (_ptr&&_owner)
delete _ptr;
_ptr = ap._ptr;
_owner = ap._owner;
ap._owner = false;
}
return *this;
}
~auto_ptr()
{
if (_ptr&&_owner)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T& operator->()
{
return _ptr;
}
protected:
T* _ptr;
//mutable 可变的,与const恰好相反
mutable bool _owner;//资源真正的拥有者(对资源有释放权力的对象)
};
}
void TestFunc()
{
bia::auto_ptr<int> ap1(new int);
bia::auto_ptr<int> ap2(ap1);
bia::auto_ptr<int> ap3;
ap3 = ap2;
}
此时,ap1和ap2都已经是野指针了。
auto_ptr(3.0)
恢复到版本1.0+其他内容
建议:什么情况下都不要使用auto_ptr
C++11:
unique_ptr
RAII+类似指针的行为+对象独占资源(防止被拷贝)
--->C++98:拷贝构造&&赋值运算符重载 (只声明不定义+访问限制private)
--->C++11:拷贝构造&&赋值运算符重载两个函数后跟=delete
缺陷:对象之间不能共享资源
//private:C++98 只声明不定义+private
unique_ptr(const unique_ptr<T>& ap);
unique_ptr<T>& operator=(const unique_ptr<T>& ap);
//C++11
unique_ptr(const unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;
shared_ptr
RAII+类似指针行为+引用计数(记录资源被对象共享的次数)
缺陷:1.不是线程安全 2.只能管理new的资源 3.存在循环引用的问题
解决:1.加入mutex来保证计数的安全性
mutex:可以保证计数的安全性||可以保证shared_ptr类本身
但不能保证shared_ptr管理资源内容的安全性
2.定制删除器------仿函数 例如:文件指针的关闭,malloc需要free掉
3.加入weak_ptr(weak_ptr是)
//定制删除器
template <class T>
class DelRef
{
public:
void operator()(T*& ptr)
{
if (ptr)
{
delete ptr;
ptr = nullptr;
}
}
};
template <class T>
class Free
{
public:
void operator()(T*& ptr)
{
if (ptr)
{
free(ptr);
ptr = nullptr;
}
}
};
class FClose
{
public:
void operator()(FILE*& pf)
{
if (pf)
{
fclose(pf);
pf = nullptr;
}
}
};
namespace bia
{
template <class T,class DF= DelRef<T>>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(nullptr)
, _pmutex(nullptr)
{
if (_ptr)
{
_pcount = new int(1);
_pmutex = new mutex;
}
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
, _pmutex(sp._pmutex)
{
if (_pcount)
{
AddRef();
}
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (this != &sp)
{
//让当前对象与资源断开
Release();
//与sp共享
_ptr = sp._ptr;
_pcount = sp._pcount;
if (_pcount)
{
AddRef();
}
return *this;
}
}
~shared_ptr()
{
Release();
}
//具有指针行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
void AddRef()
{
_pmutex->lock();
++(*_pcount);
_pmutex->unlock();
}
int SubRef()
{
_pmutex->lock();
--(*_pcount);
_pmutex->unlock();
return *_pcount;
}
void Release()
{
if (_ptr && 0 == SubRef())
{
DF d;
d(_ptr);
delete _pcount;
}
}
protected:
T* _ptr;
int* _pcount;
mutex* _pmutex;
};
}
void TestSharedPtr()
{
bia::shared_ptr<int> sp1(new int);
bia::shared_ptr<int, Free<int>> sp2((int*)malloc(sizeof(int)));
bia::shared_ptr<FILE, FClose> sp3(fopen("1.txt", "rb"));
}
以上解决了前两个问题,第三个循环引用的问题
例如:双向链表中的next与pre
互相制约,都在等对方销毁,从而造成死锁
weak_ptr:RAII+类似指针行为+引用计数
作用:配合shared_ptr使用,解决循环引用问题
注意:weak_ptr不能单独管理资源