智能指针
干什么用的?
将智能指针封装成类,利用类对象释放时调用析构函数的特点,将资源交给智能指针管理
利用对象生命周期控制资源——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; }; }