RAII思想之智能指针
Posted duikerdd
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RAII思想之智能指针相关的知识,希望对你有一定的参考价值。
RAII(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种利用对象生命周期来控制资源的技术。
简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。
这种技术是C++为了控制资源,避免内存泄漏的惯用法。
// 使用RAII思想设计的SmartPtr类 template<class T> class SmartPtr { public: SmartPtr(T* ptr = nullptr) : _ptr(ptr) {} ~SmartPtr() { if(_ptr) delete _ptr; } private: T* _ptr; };
因为C++并没有垃圾回收机制,要是忘记回收垃圾,就会造成内存泄漏。所以,RAII思想的提出,便解决了这个问题。
RAII思想设计实例:
智能指针:
起初在C++标准库里面是没有智能指针的,最早的智能指针在Boost库里面,直到C++11才加入了shared_ptr和unique_ptr以及weak_ptr。
特点: 1.RAII思想 2.具有指针行为
版本1: std:::auto_ptr
//RAII + *和->的重载 template<class T> class SmartPoint{ public: SmartPoint(T* ptr=nullptr):_ptr(ptr) {} ~SmartPoint() { std::cout<<"delete"<<_ptr<<std::endl; delete _ptr; } T& operator*(){return *_ptr}; T* operator->(){return _ptr}; private: T* _ptr; };
缺陷: 管理权转移会导致原对象指针置空,无法正常使用
详情如下,关于auto_ptr拷贝构造和赋值重载的实现:
template<class T> class AutoPtr() { //p对象资源转移后,自身置空,与资源断开联系 AutoPtr(AutoPtr<T>& p):_ptr(p._ptr) { p._ptr = NULL; } AutoPtr<T>& operator=(AutoPtr<T>& p) { //检测是否是给自己赋值 if(this != &p) { //释放当前资源 if(_ptr) delete _ptr; //将p资源转移给本对象,p的指针置空 _ ptr = p._ptr; p._ptr = NULL; } } private:
T* _ptr; };
版本2: std::unique_ptr --- 防拷贝
template<T> class UniPtr() { //简单粗暴防拷贝 //C++11防拷贝: delete UniPtr(UniPtr<T> const&) = delete; UniPtr<T>& operator=(Unique<T> const&) = delete; private: T* _ptr; };
版本3: std::shared_ptr --- 引用计数
原理: 在拷贝构造时,使用同一份计数
(1)每个shared_ptr对象内,都有一份资源被多少个对象共享的计数
(2)对象销毁时,计数-1
如果计数==0,没有其他对象共享,释放资源
如果计数>0,有其他对象共享,不释放资源
#include<thread> #include<mutex> template <class T> class SharedPtr { public: //构造 SharedPtr(T* ptr = nullptr) : _ptr(ptr) , _pRefCount(new int(1)) , _pMutex(new mutex) { // 如果是一个空指针对象,则引用计数给0 if (_ptr == nullptr) *_pRefCount = 0; } //析构 ~SharedPtr() {Release();} //拷贝构造 SharedPtr(const SharedPtr<T>& sp) : _ptr(sp._ptr) , _pRefCount(sp._pRefCount) , _pMutex(sp._pMutex) { // 如果是一个空指针对象,则不加引用计数,否则才加引用计数 if (_ptr) AddRefCount(); } // sp1 = sp2 SharedPtr<T>& operator=(const SharedPtr<T>& sp) { //if (this != &sp)等同于下面 if (_ptr != sp._ptr) { // 释放管理的旧资源 Release(); // 共享管理新对象的资源,并增加引用计数 _ptr = sp._ptr; _pRefCount = sp._pRefCount; _pMutex = sp._pMutex; if (_ptr){ AddRefCount(); } return *this; } //指针操作 T& operator*() {return *_ptr;} T* operator->() {return _ptr;} int UseCount() {return *_pRefCount;} T* Get() { return _ptr; } //原子计数: ++操作 int AddRefCount() { // 加锁或者使用加1的原子操作 _pMutex->lock(); ++(*_pRefCount); _pMutex->unlock(); return *_pRefCount; } //原子计数: --操作 int SubRefCount() { // 加锁或者使用减1的原子操作 _pMutex->lock(); --(*_pRefCount); _pMutex->unlock(); return *_pRefCount; } private: //--与判断释放操作 void Release() { // 引用计数减1,如果减到0,则释放资源 if (_ptr && SubRefCount() == 0) { delete _ptr; delete _pRefCount; } } private: int* _pRefCount; // 引用计数,用指针让所有对象共享该值 T* _ptr; // 指向管理资源的指针 mutex* _pMutex; // 互斥锁 };
关于shared_ptr的两个问题:
线程安全问题: 不是100%安全
场景: 两个线程同时读写同一个shared_ptr
1.引用计数是安全且无锁的
2.对象读写不安全,因为有两个数据成员,不能原子化操作 改进:代码上加锁控制
实例:
循环引用问题:
场景: 两个对象中的shared_ptr互相指向对方,导致两者计数变为2,pa,pb销毁之后,资源计数变为1,此时,即A内部有指向B,B内部有指向A,这样对于A,B必定是在A析构后B才析构,对于B,A必定是在B析构后才析构A,这就是循环引用问题,违反常规,导致内存泄露。
解决: 使用weak_ptr替代相互引用的shared_ptr
weak_ptr: weak_ptr是弱共享指针,其实就是share_ptr的辅助指针,不具备指针的功能。主要是为了协助 shared_ptr 工作,可以观测资源的使用情况。weak_ptr 只对 shared_ptr 进行引用,不会改变引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效。
主要功能: 1.不会改变引用计数,可以观测资源使用情况,生命周期和shared_ptr一样
2.可以使用lock来借用shared_ptr来完成操作
非new对象问题:
如果管理的是new出来的多个对象,怎么删除对象呢,shared_ptr提供了删除器
// 仿函数的删除器 --- 在创建智能指针的时候,传参释放方式 template<class T> struct FreeFunc { void operator()(T* ptr) { cout << "free:" << ptr << endl; free(ptr); } }; template<class T> struct DeleteArrayFunc { void operator()(T* ptr) { cout << "delete[]" << ptr << endl; delete[] ptr; } }; int main() { FreeFunc<int> freeFunc; shared_ptr<int> sp1((int*)malloc(4), freeFunc); //第二个参数为删除器选择 DeleteArrayFunc<int> deleteArrayFunc; shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc); return 0; }
以上是关于RAII思想之智能指针的主要内容,如果未能解决你的问题,请参考以下文章