智能指针
干什么用的?
将智能指针封装成类,利用类对象释放时调用析构函数的特点,将资源交给智能指针管理
利用对象生命周期控制资源——RAII
如何过渡到下面这个问题的?
智能指针的拷贝,默认是浅拷贝,即多个智能指针会指向同一份资源。这是没有问题的,符合智能指针的定义
这样做的问题是,同份资源会被多次析构
为了解决智能指针拷贝(拷贝构造、赋值拷贝)导致的多次析构的问题,衍生出了如下3种智能指针
auto_ptr 1
任何时候只允许一个智能指针对象对一份资源做管理
模拟实现auto_ptr
template<class T>
class auto_ptr {
public:
//构造
auto_ptr(T* ptr=nullptr):_ptr(ptr){}
//拷贝构造
auto_ptr(auto_ptr<T>& ap)
{
_ptr = ap._ptr;
ap._ptr = nullptr;
}
//赋值拷贝 需要考虑特殊情况
auto_ptr& operator=(auto_ptr& ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
return *this;
}
//析构函数
~auto_ptr()
{
if (_ptr != nullptr)
{
delete _ptr;
_ptr = nullptr;
}
}
//->
T* operator->()
{
return _ptr;
}
//*
T& operator* ()
{
return *_ptr;
}
private:
T* _ptr;
};
}
int* a =nullptr;
delete a 为什么不会报错
因为 C++ 标准规定对空指针进行删除操作时,会忽略这个操作,不会引发运行时错误。
int b=0;
int *a =&b
delete a 为什么会报错
因为delete是用于释放new出来的堆空间(动态分配出来的空间),而a指向的空间属于栈上的空间
比较拷贝构造与赋值拷贝之间的差异
为什么要delete _ptr?
因为一个auto_ptr对象只能管理一个资源,所以要先释放自身管理的资源
为什么要这样做?
因为一个资源只能被一个auto_ptr对象管理。
为什么不delete ap._ptr,因为该auto_ptr还有可能被赋值
特殊情况考虑
2个auto_ptr对象的_ptr都为nullptr(表明这两个对象没有管理任何资源)
就会出现delete nullptr的情况,所以要在构造函数部分加判空判断
int* a =nullptr;
delete a 为什么不会报错
因为 C++ 标准规定对空指针进行删除操作时,会忽略这个操作,不会引发运行时错误。
int b=0;
int *a =&b
delete a 为什么会报错
因为delete是用于释放new出来的堆空间(动态分配出来的空间),而a指向的空间属于栈上的空间
为什么拷贝构造没有delete _ptr
因为管理资源的对象还没有被创建出来,也就是说该对象还没有管理任何资源
总结:拷贝构造与赋值拷贝
相同点就是:它们都要保证一个资源只能被一个对象管理
不同点就是:拷贝构造是一个已存在的对象拷贝给一个尚未存在的对象,所以不用释放
赋值拷贝是两个已存在的对象间的拷贝,所以要释放
unique_ptr 0
防拷贝,也就是说该智能指针禁止拷贝
具体就是将拷贝构造与赋值构造这两个函数禁用
template <class T>
class unique_ptr {
public:
//默认构造 表示没有管理任何资源
unique_ptr(T* ptr = nullptr) :_ptr(ptr) {}
//析构函数
~unique_ptr()
{
if (_ptr != nullptr)
{
delete _ptr;
_ptr = nullptr;
}
}
//->
T* operator->()
{
return _ptr;
}
//*
T& operator* ()
{
return *_ptr;
}
unique_ptr(unique_ptr<T>& up) = delete;
unique_ptr& operator=(unique_ptr<T>& up) = delete;
private:
T* _ptr;
};
shared_ptr >1
允许拷贝,利用计数器管理使用同一份资源的智能指针,当计数器等于0时才释放智能指针管理的资源
这意味着什么?
这意味着可以有多个shared_ptr对象管理同一份资源
template<class T>
class shared_ptr {
public:
//构造
shared_ptr(T* ptr=nullptr):_ptr(ptr),_pcount(new int(1)){}
//拷贝构造
shared_ptr(shared_ptr& sp)
{
_ptr = sp._ptr;
_pcount = sp._pcount;
*_pcount++;
}
//赋值构造
shared_ptr& operator=(shared_ptr& sp)
{
if (sp._ptr != _ptr)
{
if (-- ( * _pcount) == 0)
{
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
(* _pcount)++;
}
return *this;
}
//析构
~shared_ptr()
{
if (--(*_pcount) == 0)
{
if (_ptr != nullptr)//特殊情况,_ptr有可能指向空,就对他做处理
{
delete _ptr;
_ptr = nullptr;
}
delete _pcount;
_pcount = nullptr;
}
}
//->
T* operator->()
{
return _ptr;
}
//*
T& operator* ()
{
return *_ptr;
}
//引用计数
int use_count()
{
return *(_pcount);
}
private:
T* _ptr;
int* _pcount;
};
}
循环引用问题
shared_ptr存在循环引用问题,如下例
将ListNode交给shared_ptr管理
运行结果 没有如预期结果释放资源
原因:
程序运行时
程序 结束后
为什么shared_ptr<ListNode>_next 与shared_ptr<ListNode>_prev不在程序结束的时候释放呢?因为它们是new出来的,需要手动释放
weak_ptr
解决循环引用
上述问题的关键其实就是多了一个shared_ptr,导致程序结束时计数器不为0
weak_ptr 接受一个shared_ptr对象 但是计数器不+1
实现:
template<class T>
class weak_ptr {
public:
//构造
weak_ptr(T* ptr=nullptr):_ptr(ptr){}
//拷贝构造
weak_ptr(shared_ptr<T>& sp)
{
if(sp.use_count!=0)
_ptr = sp.get();
}
//赋值拷贝
weak_ptr& operator=(shared_ptr<T>& sp)
{
if (sp.use_count() != 0)
{
_ptr = sp.get();
}
return *this;
}
//析构
~weak_ptr()
{
_ptr = nullptr;
}
//->
T* operator->()
{
return _ptr;
}
//*
T& operator* ()
{
return *_ptr;
}
private:
T* _ptr;
};
}