目录
RAII
C++标准库智能指针
auto_ptr
unique_ptr
循环引用问题
weak_ptr
RAII
RAII是Resource Acquisition Is Initialization的缩写,他是一种资源管理的类的设计思想
本质是利用对象生命周期来管理获取到的动态资源,避免资源泄漏
RAII在获取资源时把资源委托给一个对象,控制着对资源的访问,资源在对象的生命周期内始终保持有效,最后在对象析构时释放资源,保证了资源的正常释放,避免资源泄露的问题
智能指针类就满足RAII的设计思路
C++标准库智能指针
C++标准库中的智能指针都在<memory>这个头文件下面
智能指针有好几种,除了weak_ptr他们都符合RAII和像指针一样的访问行为,原理上是解决智能指针拷贝时思路的不同
auto_ptr是C++98设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源管理权转给拷贝对象,这会导致他会让被拷贝对象悬空,导致访问报错的问题,强烈不建议使用auto_ptr
unique_ptr是C++11设计出来的智能指针,翻译是唯一指针,他的特点是不支持拷贝,只支持移动,不需要拷贝的场景就非常建议使用他
shared_ptr是C++11设计出来的智能指针,翻译是共享指针,他的特点是支持拷贝,也支持移动,如果需要拷贝的场景就需要使用他。底层实现方式是引用计数
weak_ptr是C++11设计出来的智能指针,翻译是弱指针,他不同于上面的智能指针,他不支持RAII,也就意味着不能用他来直接管理资源,weak_ptr的产生本质是要解决share_ptr的一个循环引用导致内存泄漏的问题
auto_ptr
template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){// 管理权转移sp._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
unique_ptr
template<class T>
class unique_ptr
{
public:explicit unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针⼀样使⽤T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;unique_ptr(unique_ptr<T>&& sp):_ptr(sp._ptr){sp._ptr = nullptr;}unique_ptr<T>& operator=(unique_ptr<T>&& sp){delete _ptr;_ptr = sp._ptr;sp._ptr = nullptr;}
private:T* _ptr;
};
shared_ptr
template<class T>
class shared_ptr
{
public:explicit shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}template<class D>shared_ptr(T* ptr, D del):_ptr(ptr),_pcount(new int(1)),_del(del){}shared_ptr(shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount),_del(sp._del){(*_pcount)++;}void release(){if (--(*_pcount) == 0){_del(_ptr);delete _pcount;_ptr = _pcount = nullptr;}}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;_del = sp._del;(*_pcount)++;}}~shared_ptr(){release();}T* get() const{return _ptr;}int use_count() const{return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;int* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; }; // 删除器
};
shared_ptr管理一份资源就需要一份引用计数,所有引用计数用静态成员的方式是无法解决的,而是采用了堆上动态开辟的方式
当构造智能指针对象时来一份资源就要new一个引用计数,多个shared_ptr指向资源时就++引用计数即可,当shared_ptr析构时只需要--引用计数,当引用计数--到0后代表当前析构的shared_ptr是最后一个管理资源的对象,则析构资源
循环引用问题
若有如下代码
struct ListNode
{int data;std::shared_ptr<ListNode> _next;std::shared_ptr<ListNode> _prev;
};int main()
{std::shared_ptr<ListNode> n1(new ListNode);std::shared_ptr<ListNode> n2(new ListNode);n1->next = n2;n2->prev = n1;return 0;
}
n1和n2析构后,管理两个节点的引用计数减到1
右边的节点释放需要左边的next不再管理,next析构后右边的节点就可以释放
next是左边的节点,而左边的节点释放需要右边的prev不再管理,prev释放后next才可以释放
prev是右边的节点,右边的节点需要右边的节点释放,至此逻辑上就形成了循环引用,谁都不会释放,导致内存泄漏
解决这个问题只需要把ListNode结构体中的next和prev改成weak_ptr,weak_ptr绑定到shared_ptr时不会增加它的引用计数,next和prev不再参与资源释放管理逻辑,就成功的打破了引用循环
struct ListNode
{int data;std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;
};
shared_ptr的线程安全问题
shared_ptr的引用计数在堆上,如果多个shared_ptr对象在多个线程中进行拷贝析构的操作修改引用计数,就会存在线程安全问题,所以shared_ptr引用计数是需要加锁或者原子操作保证线程安全的
我们可以在shared_ptr引用计数从int*改成atomic<int*>就可以保证引用计数的线程安全问题,或者使用互斥锁加锁也可以
private:T* _ptr;atomic<int*> _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; }; // 删除器
};
weak_ptr
template<class T>
class weak_ptr
{
public:weak_ptr(){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}
private:T * _ptr = nullptr;
};
weak_ptr不支持RAII,不支持访问资源,只支持绑定到shared_ptr,且不增加shared_ptr的引用计数,用来解决shared_ptr循环引用的问题
weak_ptr没有重载operator*和operator->等,因为它不参与资源管理,如果它绑定的shared_ptr已经释放了资源,weak_ptr再去访问资源是很危险的
完