RAII是基于对象的生命周期来控制程序资源(如内存,网络连接,互斥量)等,在对象构造时获取对象的资源,在对象析构的时候释放资源。实际上就是把管理一个资源责任交给一个对象,这样做的好处是:不需要显式的释放资源,对象在其生命周期内一直有效。
使用这种思想,我们可以模拟实现一个SmartPtr类实现对资源的管理。
- #include<iostream>
- using namespace std;
- struct Date
- {
- int _year;
- int _month;
- int _day;
- };
- namespace Etta
- {
- template<class T>
- class SmartPtr
- //这种指针无法对对象进行拷贝和赋值。
- //如果拷贝构造和赋值,就会重新开空间,实现深拷贝,且对相同资源做了多次管理,
- //不可靠且浪费资源,由此引出了auto_ptr
- {
- public:
- SmartPtr(T*ptr=nullptr)
- :_ptr(ptr)
- {}
- ~SmartPtr()
- {
- if(_ptr)
- {
- delete _ptr;
- }
- }
- private:
- T*_ptr;
- };
- void test()
- {
- //把对象成员交给类取管理,当出了作用域以后,就对类析构,释放对象资源
- SmartPtr<int> sp1(new int);
- *sp1 = 10;
- cout<<*sp1<<endl;
- SmartPtr<Date> sparray(new Date);
- sparray->_year = 2018;
- sparray->_month = 1;
- sparray->_day = 1;
- }
- }
上述SmartPtr不能成为智能指针,因为不具有解引用,->功能,所以我们需要重载->和*才能算是一个智能指针。
- #include"SmartPtr.h"
- // auto_ptr的问题:当对象拷贝或者赋值后,前面的对象就悬空了
- // C++98中设计的auto_ptr问题是非常明显的,所以实际中很多公司明确规定了不能使用auto_ptr
- namespace Etta
- {
- template<class T>
- class auto_ptr
- {
- public:
- auto_ptr(T*ptr=nullptr)
- :_ptr(ptr)
- {}
- ~auto_ptr()
- {
- if(_ptr)
- {
- delete _ptr;
- }
- }
- //拷贝构造和赋值、
- // 一旦发生拷贝,就将ap中资源转移到当前对象中,然后另ap与其所管理资源断开联系,
- // 这样就解决了一块空间被多个对象使用而造成程序奔溃问题
- auto_ptr(auto_ptr<T>&ap)
- :_ptr(ap._ptr)
- {
- ap._ptr=nullptr;
-
- }
- auto_ptr<T>& operator=(const auto_ptr<T>&ap)
- {
- if(this!=&ap)
- {
- if(_ptr)
- {
- delete _ptr;
- }
- _ptr=ap._ptr;
- ap._ptr=nullptr;
- }
- return *this;
- }
- T*operator->()
- {
- return _ptr;
- }
-
- T&operator*()
- {
- return *_ptr;
- }
- private:
- T* _ptr;
- };
- void test_auto_ptr()
- {
- auto_ptr<Date> ap(new Date);
- // 现在再从实现原理层来分析会发现,这里拷贝后把ap对象的指针赋空了,导致ap对象悬空
- // 通过ap对象访问资源时就会出现问题。
- auto_ptr<Date> copy(ap);
- ap->_year = 2018;
- }
-
- }
但是auto_ptr的赋值和拷贝构造是通过对资源的管理权转移,导致原指针悬空,所以很多公司禁止了使用这种智能指针,所以衍生出unique_ptr。
智能指针的特性:
RAII特性
具有像指针一样的行为,支持->和*。
unique_ptr的实现原理:简单粗暴的防拷贝
- //简单粗暴的防拷贝,简化模拟实现了一份UniquePtr来了解
- namespace Etta
- {
- template<class T>
- class unique_ptr
- {
- public:
- // RAII
- unique_ptr(T* ptr)
- :_ptr(ptr)
- {}
-
- ~unique_ptr()
- {
- cout << "delete:" << _ptr << endl;
- delete _ptr;
- }
-
- // 可以像指针一样使用
- T& operator*()
- {
- return *_ptr;
- }
-
- T* operator->()
- {
- return _ptr;
- }
-
- unique_ptr(const unique_ptr<T>&) = delete;
- unique_ptr<T>operator=(const unique_ptr<T>&) = delete;
- private:
- T* _ptr;
- };
-
- void test_unique_ptr()
- {
- //std::unique_ptr<int> sp1(new int);
- // 拷贝
- //std::unique_ptr<int> sp2(sp1);
-
- unique_ptr<int> sp1(new int);
- // 拷贝
- //bit::unique_ptr<int> sp2(sp1);
- }
- }
缺点就是不支持拷贝和赋值,为了更加靠谱和支持拷贝构造,C++11提出了shared_ptr
是通过引用计数的方式来实现多个shared_ptr对象之间共享资源
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
5,因为存在计数,所以多个线程访问时,容易导致两个线程同时+,本来是3,但实际上变成2,所以为了防止这种情况,需要在拷贝和赋值的时候加锁处理。
模拟实现:
- #include<thread>
- #include<mutex>
- #include<iostream>
- using namespace std;
- namespace Etta
- {
- template<class T>
- class share_ptr
- {
- public:
- share_ptr(T* ptr=nullptr)
- :_ptr(ptr)
- ,_count(new int(1))
- ,_mtx(new mutex)
- {}
- ~share_ptr()
- {
- ReleaseRef();
- }
- share_ptr(const share_ptr<T>&sp)
- :_ptr(sp._ptr)
- ,_mtx(sp._mtx)
- ,_count(sp._count)
- {
- AddRef();
- }
- //将sp赋值给this
- //先减去_ptr的计数,再把sp的空间给给_ptr,再对_ptr加加
- share_ptr<int>&operator=(const share_ptr<T>&sp)
- {
- if(_ptr!=sp._ptr)
- {
- ReleaseRef();
- _ptr=sp._ptr;
- _mtx=sp._mtx;
- _count=sp._count;
- AddRef();
- }
- return *this;
- }
-
- T* get() const
- {
- return _ptr;
- }
- T&operator*()
- {
- return *_ptr;
- }
- T*operator->()
- {
- return _ptr;
- }
- int use_count()
- {
- return *_count;
- }
- private:
- T *_ptr;
- mutex* _mtx;
- int *(_count);
- void AddRef()
- {
- //为什么要加锁,因为不加锁,在两个线程同时拷贝一个对象的话,如果都进来++、
- //容易导致产生加了2次,但实际上值为2
- //产生线程错误
- _mtx->lock();
- ++(*_count);
- _mtx->unlock();
- }
- void ReleaseRef()
- {
- _mtx->lock();
- bool flag=false;
- if(--(*_count)==0)
- {
- cout << "delete:" << _ptr << endl;
- delete _ptr;
- delete _count;
- flag=true;
- }
- _mtx->unlock();
- if(flag==true)
- {
- delete _mtx;
- }
- }
- };
- }
先看这个问题:我们使用shared_ptr对节点进行管理。
看看跑起来结果怎么样。
- struct ListNode
- {
- std::shared_ptr<ListNode> _next;
- std::shared_ptr<ListNode> _prev;
- int _val;
-
- ~ListNode()
- {
- cout << "~ListNode()"<<endl;
- }
- };
- void test_shared_ptr_cycle()
- {
- std::share_ptr<ListNode>node1(new ListNode);
- std::share_ptr<ListNode>node2(new ListNode);
- cout << node1.use_count() <<endl;
- cout << node2.use_count() << endl;
-
-
- node1->_next=node2;
- node2->_prev=node1;
-
- cout << node1.use_count() <<endl;
- cout << node2.use_count() << endl;
- }
可以看到程序会崩掉,在最后没有调用ListNode的析构,导致这两个节点未被释放,
此时,
解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
原理就是:node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数
不参与资源管理,不增加shared_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;
- }
- private:
- T* _ptr;
- };
智能指针主要有以下思想:
RAII: RAII是基于对象的生命周期来控制程序资源,构造的时候管理,析构的时候释放。
shared_ptr:对指针的拷贝和赋值通过一个int*(count)进行++计数,析构--,加锁控制线程安全。
weak_ptr;对象内成员不参与资源的管理,只是作为一个指针使用。