> 文章列表 > C++:智能指针(auto_ptr/unique_ptr/shared_ptr/weak_ptr)

C++:智能指针(auto_ptr/unique_ptr/shared_ptr/weak_ptr)

C++:智能指针(auto_ptr/unique_ptr/shared_ptr/weak_ptr)

为什么需要智能指针

C++没有垃圾回收机制。

#include<iostream>
using namespace std;int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?int* p1 = new int[10];int* p2 = nullptr;int* p3 = nullptr;try{p2 = new int[20];}catch (...){delete[] p1;throw;}try{p3 = new int[10];}catch (...){delete[] p1;delete[] p2;throw;}try{cout << div() << endl;}catch (...){delete[] p1;delete[] p2;delete[] p3;throw;}delete[] p1;delete[] p2;
}int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

在多次new时有可能抛异常,就需要一层一层捕获异常并释放资源还需要条件判断,非常麻烦,否则抛异常之后delete多了或者少了都不行。一不小心就内存泄露了。

如何避免内存泄漏

1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps: 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。

2. 采用RAII思想或者智能指针来管理资源。

3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

智能指针的使用及原理

智能指针指针包含两个部分:RAII(通过构造和析构管理资源)和像指针一样的操作符重载。

智能指针的本质时将资源生命周期与对象绑定

下面来见一见RAII的智能指针:

template<class T>
class smart_ptr
{
public:smart_ptr(T* ptr):_ptr(ptr){}~smart_ptr(){delete _ptr;}private:T* _ptr;
};

我们再来实现像指针一样的部分:

template<class T>class smart_ptr{public://RAIIsmart_ptr(T* ptr):_ptr(ptr){}~smart_ptr(){std::cout << "delete" << _ptr << std::endl;delete _ptr;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}private:T* _ptr;};
int main()
{szg::smart_ptr<Date> sp1(new Date());cout << sp1->_year << ":" << sp1->_month << ":" << sp1->_day << endl;++sp1[0]._year;++(*sp1)._month;++sp1->_day;cout << sp1->_year << ":" << sp1->_month << ":" << sp1->_day << endl;return 0;
}//0:0 : 0
//1 : 1 : 1
//delete00732778

问题一:拷贝构造会重复析构

智能指针的方式也会产生一些问题,比如拷贝构造和赋值会重复析构。

int main()
{szg::smart_ptr<Date> sp1(new Date());szg::smart_ptr<Date> sp2(sp1);return 0;
}
//delete00CE27C0
//delete00CE27C0
//直接报错

在学习解决这个问题前先了解一下智能指针的发展历史:

// C++智能指针发展历史// C++98 auto_ptr 资源管理权转移-->对象悬空 很多公司明确要求不能使用它
// boost scoped_ptr 防拷贝
// boost shared_ptr/weak_ptr // 引用计数// C++11 unique_ptr 防拷贝
// C++11 shared_ptr/weak_ptr // 引用计数

auto_ptr

最先提出的是auto_ptr,它是通过资源管理权转移解决重复析构问题的:

	template<class T>class auto_ptr{public://RAIIauto_ptr(T* ptr):_ptr(ptr){}~auto_ptr(){if (_ptr){std::cout << "delete" << _ptr << std::endl;delete _ptr;}}auto_ptr(auto_ptr<T>& ap):_ptr(nullptr){std::swap(_ptr, ap._ptr);}auto_ptr<T>& operator=(auto_ptr<T>& ap) = delete;//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}private:T* _ptr;};

貌似解决了问题,但会导致对象悬空,不建议使用。

unique_ptr

unique_ptr脱胎于boost库(C++标准库的预备库)的scoped_ptr,通过禁用拷贝的方式解决重复析构问题。

	template<class T>class unique_ptr{public://RAIIunique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){std::cout << "delete" << _ptr << std::endl;delete _ptr;}}unique_ptr(unique_ptr<T>& ap) = delete;unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}private:T* _ptr;};

shared_ptr/weak_ptr

shared_ptr/weak_ptr使用引用计数方式防止拷贝构造。

新开辟一块区域作为引用计数的空间:

template<class T>class shared_ptr{public://RAIIshared_ptr(T* ptr):_ptr(ptr), _pcnt(new int(1)),_pmutex(new std::mutex){}void release(){bool flag = false;_pmutex->lock();if (--(*_pcnt) == 0 && _ptr){delete _ptr;delete _pcnt;flag = true;}_pmutex->unlock();if (flag){delete _pmutex;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcnt(sp._pcnt),_pmutex(sp._pmutex){_pmutex->lock();++(*_pcnt);_pmutex->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcnt = sp._pcnt;_pmutex = sp._pmutex;_pmutex->lock();++(*_pcnt);_pmutex->unlock();}return *this;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}private:T* _ptr;int* _pcnt;std::mutex* _pmutex;};
int main()
{szg::shared_ptr<int> sp1(new int(0));szg::shared_ptr<int> sp2(sp1);szg::shared_ptr<int> sp3(sp2);(*sp1)++;(*sp2)++;cout << *sp1 << endl;cout << *sp2 << endl;sp1 = sp2;szg::shared_ptr<int> sp4(new int(10));szg::shared_ptr<int> sp5(sp4);sp1 = sp4;return 0;
}

std::shared_ptr的循环引用

循环引用分析:

1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动 delete。

2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。

3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上 一个节点。

4. 也就是说_next析构了,node2就释放了。

5. 也就是说_prev析构了,node1就释放了。

6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev 属于node2成员,所以这就叫循环引用,谁也不会释放。

 

struct ListNode
{ListNode(){val = 0;}int val;szg::shared_ptr<ListNode> _next;szg::shared_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};void test_shared_ptr2()
{szg::shared_ptr<ListNode> n1(new ListNode);szg::shared_ptr<ListNode> n2(new ListNode);n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;
}int main()
{test_shared_ptr2();return 0;
}

循环引用的本质是节点内含智能指针,且节点内的智能指针互相引用(指向对方)。

由于计数引用导致了:

因为你中有我,我不能先释放

因为我中有你,你不能先释放

导致都不能释放,直接造成内存泄漏

循环引用是shared_ptr的死穴,要用weak_ptr解决。

weak_ptr

weak_ptr解决循环引用是通过使weak_ptr可以指向资源,但是不增加引用计数实现的。不支持管理资源,只用于share_ptr的拷贝,是shared_ptr的小弟。

	template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}public:T* _ptr;};
struct ListNode
{ListNode(){val = 0;}int val;// 可以指向资源/访问资源,不参与资源管理,不增加引用计数szg::weak_ptr<ListNode> _next;szg::weak_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};void test_shared_ptr2()
{szg::shared_ptr<ListNode> n1(new ListNode);szg::shared_ptr<ListNode> n2(new ListNode);n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;
}int main()
{test_shared_ptr2();return 0;
}

定制删除器

定制一个资源释放的方式,毕竟newnew[]出来的资源的释放方式是不一样的,得匹配deletedelete[]

只要使用包装器对象保存释放资源的方法即可:

template<class T>class shared_ptr{public://RAIIshared_ptr(T* ptr = nullptr, const std::function<void(T*)> func = [](T* t) {delete t;}):_ptr(ptr), _pcnt(new int(1)),_pmutex(new std::mutex){_func = func;}void release(){bool flag = false;_pmutex->lock();if (--(*_pcnt) == 0 && _ptr){_func(_ptr);delete _pcnt;flag = true;}_pmutex->unlock();if (flag){std::cout << "delete" << std::endl;delete _pmutex;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcnt(sp._pcnt),_pmutex(sp._pmutex){_func = sp._func;_pmutex->lock();++(*_pcnt);_pmutex->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcnt = sp._pcnt;_pmutex = sp._pmutex;_func = sp._func;_pmutex->lock();++(*_pcnt);_pmutex->unlock();}return *this;}int use_count(){_pmutex->lock();int ret = (*_pcnt);_pmutex->unlock();return ret;}T* get() const{return _ptr;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}private:T* _ptr;int* _pcnt;std::mutex* _pmutex;std::function<void(T*)> _func;};
int main()
{szg::shared_ptr<string> sp1(new string[10], [](string* s) {delete[] s;});szg::shared_ptr<string> sp2(sp1);szg::shared_ptr<string> sp3(sp2);(*sp1) = "111111";(*sp2) = "222222";cout << *sp1 << endl;cout << *sp2 << endl;sp1 = sp2;szg::shared_ptr<string> sp4(new string[10], [](string* s) {delete[] s;});szg::shared_ptr<string> sp5(sp4);sp1 = sp4;return 0;
}