目录
以c++的异常处理为例看看throw catch用法。有时,一个用new开出的空间用完还没delete呢。突然throw一个错误,程序就跑没了,造成了内存泄漏。为了解决这个问题,引入了智能指针的概念,作用是为了让这块空间可以自动释放。智能指针实际就是用一个类来管理这块空间,利用析构函数来释放空间。同时利用重载-> = *来实现一般指针相同的功能。
因为是用一个类来进行管理,那么拷贝怎么办?我们都知道普通指针是采用的浅拷贝的方式,智能指针也得有普通指针的功能啊。但是,若是简单的完成浅拷贝就必然会导致同一块空间多次释放的问题。看图:
ap1和ap2都指向了同一块资源,俩再一析构,就全完了。
a.auto_ptr:直接转移管理权(摆烂做法,不推荐)。
b.unique_ptr:直接不让你拷贝,拷贝就报错。(太粗暴,没有解决问题,也不推荐)。
c.shared_ptr:用引用计数的方式解决了这个问题。(推荐,完美解决问题)。
上菜:
- #include
- #include
- #include
-
- using namespace std;
- class A
- {
- public:
- ~A()
- {
- cout << "~A true" << endl; //验证析构次数
- }
- int _a=0; //为了下面验证才搞成public的
- };
- namespace cpp
- {
- template<class T>
- class auto_ptr
- {
- private:
- T* _ptr;
- public:
- auto_ptr(T* ptr=nullptr)
- :_ptr(ptr)
- {}
- auto_ptr(auto_ptr
& ap) //拷贝构造 - :_ptr(ap._ptr)
- {
- ap._ptr = nullptr;
- //auto_ptr用了非常搞的方式来拷贝,直接将资源管理权给拷贝对象,我摆烂辣!
- //被拷贝的对象直接置空,防止2次析构。
- }
-
- ~auto_ptr()
- {
- if (_ptr) //非空删除空间
- {
- delete _ptr;
- _ptr = nullptr;
- }
- }
- auto_ptr
& operator=(auto_ptr& ap) - {
- if (&ap != this) //要考虑自身赋值的问题
- {
- //与拷贝构造不同的是,先得把自己释放了,才可以拿别人的,防止内存泄漏。
- if (_ptr)
- {
- delete _ptr;
- }
- _ptr = ap._ptr;
- ap._ptr = nullptr;
- }
- return *this;
- }
- T* operator->()
- {
- return _ptr;
- }
- T& operator*()
- {
- return *_ptr;
- }
-
- };
- }
再来看看测试:
- int main()
- {
- //看看能不能和普通指针一样用
- cpp::auto_ptr ap1 = new(A);
- ap1->_a++;
- cout << ap1->_a << endl;
-
- //看看拷贝多次析构解决了没
- cpp::auto_ptr ap2(ap1);
- ap2->_a++;
- cout << ap2->_a << endl;
-
-
- }
要是再调用ap1就崩溃,这auto_ptr不太行。
上菜:
- template<class T>
- class unique_ptr
- {
- private:
- T* _ptr;
- public:
- unique_ptr(T* ptr)
- :_prt = ptr;
- {}
- unique_ptr(unique_ptr
& up)=delete; //用delete关键字不让你用 - unique_ptr
& operator=(unique_ptr& up) = delete; - ~unique_ptr()
- {
- if (_ptr) //非空删除空间
- {
- delete _ptr;
- _ptr = nullptr;
- }
- }
- T* operator->()
- {
- return _ptr;
- }
- T& operator*()
- {
- return *_ptr;
- }
- };
这都没啥好试的,直接拷贝用不了。
这么巧妙的shared_ptr得好好说说,我们想到计数引用,估计都会先想到用一个静态(static)的count来计数。但这是搞不定的,每个类都搞一个,无法共享count。所以,我们采用一个共享的空间来存储count。
- template<class T>
- class shared_ptr
- {
- private:
- T* _ptr;
- int* _pCount; //用一块共享空间计数
- public:
- shared_ptr(T* ptr=nullptr)
- :_ptr(ptr)
- ,_pCount(new int(1)) //默认构造给1
- {}
- shared_ptr(shared_ptr
& sp) - :_ptr(sp._ptr)
- ,_pCount(sp._pCount)
- {
- ++(*_pCount); //拷贝构造完成记得++count
- }
- shared_ptr
& operator=(shared_ptr& sp) - {
- if (&sp != this) //首先保证不是自己拷贝自己。
- {
- if (--(*sp._pCount) == 0) //当*pCount == 1时,要释放空间。
- {
- delete sp._ptr;
- delete sp._pCount;
- }
- _ptr = sp._ptr;
- _pCount = sp._pCount;
- ++(*_pCount);
- }
- return *this;
- }
- ~shared_ptr()
- {
- if ((--*_pCount) == 0)
- {
- delete _ptr;
- delete _pCount;
- }
- }
- T* operator->()
- {
- return _ptr;
- }
- T& operator*()
- {
- return *_ptr;
- }
- };
- }
看看测试:
- int main()
- {
- cpp::shared_ptr ap1 = new(A);
- ap1->_a++;
- cpp::shared_ptr ap2(ap1);
- ap2->_a++;
- cpp::shared_ptr ap3 = ap2;
- ap3->_a++;
- ap3 = ap3;
-
- cout << ap2->_a << endl; //和auto_ptr不同的是完全解决了拷贝问题。
- cout << ap3->_a << endl;
-
-
- }
完美,以后再也不用担心漏掉delete啦,用完自动就释放。
- class Node
- {
- public:
- ~Node()
- {
- cout << "~Node true" << endl; //验证析构次数
- }
- int _val = 0;
- shared_ptr
_next = nullptr; //必须用智能指针才能对应上类型 - shared_ptr
_prev = nullptr; - };
-
- int main()
- {
- shared_ptr
sp1 (new(Node)); - shared_ptr
sp2 (new(Node)); -
- //形成了相互引用
- sp1->_next = sp2;
- sp2->_prev = sp1;
-
- cout << sp1.use_count() << endl; //use_count表示引用计数的大小
- cout << sp2.use_count() << endl;
- }
我注释掉一行,结果没问题。
我去掉注释
就析构不了。
这就是引用计数的缺点,如图:
两边count都变成了1,就僵住了。左边整个空间想要释放需要右边空间的_prev释放;右边空间的_prev释放需要右边整个空间释放;右边整个空间想要释放需要左边空间的_next释放;左边空间的_next释放需要左边整个空间释放。走边整个空间的释放......(串上了,没完没了了,谁也释放不了)。
这时,我们需要用弱化指针weak_ptr,直接让_next和_prev无法改变空间的引用计数。
shared_ptr是可以接收weak_ptr的。
看图查看差别:
ok,问题解决,直接让他俩count都是1。
不得不说,这c++为了填上没垃圾回收器的坑,搞了一套这么复杂的智能指针出来。有价值的就是shared_ptr啦。智能指针也会带来问题(相互引用)又得用weak_ptr来解决。不过,学会这套机制对于我们理解内存管理有很大的帮助。