C++进阶---智能指针
Posted 4nc414g0n
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++进阶---智能指针相关的知识,希望对你有一定的参考价值。
智能指针
RAII
RAII
(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术,在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。这样不需要显式地释放资源,同时,采用这种方式,对象所需的资源在其生命期内始终保持有效
示例
:
在除0操作抛出异常的时候:如果在当前函数体内申请过资源,就需要进行一次try-catch以下并在catch内加上delete语句释放资源double div() double a, b; cin >> a >> b; if (b == 0) throw invalid_argument("0"); return a / b; void f1() int* p = new int; try cout << div() << endl; catch (...) delete p; cout << p << endl; throw; delete p; cout << p << endl; int main() try f1(); catch (exception& e) cout << e.what() << endl; return 0;
但如果将这个申请的资源交给智能指针管理,在生命周期结束后会自动调用它的析构函数释放资源达到效果:
double div() double a, b; cin >> a >> b; if (b == 0) throw invalid_argument("0"); return a / b; void f1() SmartPtr<int> sp(new int); *sp = 10; cout << *sp << endl; cout << div() << endl; int main() try f1(); catch (exception& e) cout << e.what() << endl; return 0;
smartptr:
template<class T> class SmartPtr public: // 1、RAII // 2、重载operator* 和 operator-> 用起来像指针一样 SmartPtr(T* ptr) :_ptr(ptr) ~SmartPtr() delete _ptr; cout <<"~smart"<< _ptr << endl; T& operator*() return *_ptr; T* operator->() return _ptr; private: T* _ptr; ;
C++98的auto_ptr
上面的智能指针并没有解决所有问题,当尝试拷贝这个指针的时候,会出错(两次析构)
C++98版本的库中就提供了auto_ptr的智能指针
auto_ptr的使用
auto_ptr通过管理权转移的方式防止了两次析构的问题,图中可见当转移后原来的dt被置空了(delete空不会出错)
auto_ptr的问题:
当对象拷贝或者赋值后,前面的对象悬空
,当尝试修改dt时出错,这是一种不好的设计
auto_ptr模拟实现
auto_ptr的实现原理:管理权转移
template<class T> class auto_ptr public: // 1、RAII // 2、重载operator* 和 operator-> 用起来像指针一样 auto_ptr(T* ptr) :_ptr(ptr) // sp2(sp1) 管理权转移 auto_ptr(auto_ptr<T>& sp) :_ptr(sp._ptr) sp._ptr = nullptr; // ap2 = ap3; auto_ptr<T>& operator=(auto_ptr<T>& ap) if (this != &ap)//防止自己赋给自己 delete _ptr; _ptr = ap._ptr; ap._ptr = nullptr; return *this;//ap2 ~auto_ptr() if (_ptr) delete _ptr; cout << _ptr << endl; T& operator*() return *_ptr; T* operator->() return _ptr; private: T* _ptr; ;
C++11的unique_ptr, shared_ptr, weak_ptr
C++11的unique_ptr, shared_ptr, weak_ptr 是由 boost库中的scope_ptr, shared_ptr, weak_ptr分别演变过来的
unique_ptr
unique_ptr的使用
针对不需要拷贝的场景:
直接防拷贝
unique_ptr的模拟实现
模拟实现:
template<class T> class unique_ptr public: // 1、RAII // 2、重载operator* 和 operator-> 用起来像指针一样 unique_ptr(T* ptr) :_ptr(ptr) // 防拷贝 unique_ptr(const unique_ptr<T>& up) = delete; unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete; ~unique_ptr() if (_ptr) delete _ptr; cout << _ptr << endl; T& operator*() return *_ptr; T* operator->() return _ptr; private: T* _ptr; ;
注意:
// C++98防拷贝的方式:只声明不实现+声明成私有 unique_ptr(unique_ptr<T> const &); unique_ptr & operator=(unique_ptr<T> const &); // C++11防拷贝的方式:delete unique_ptr(unique_ptr<T> const &) = delete; unique_ptr & operator=(unique_ptr<T> const &) = delete;
shared_ptr
shared_ptr的使用
成员函数:
- get:返回所管理资源的地址(指针),存储的指针指向 shared_ptr 对象解引用的对象,通常与其拥有的指针相同
- use_count:返回所有的引用计数(包括自己)
- unique:判断该shared_ptr是否是唯一管理资源的智能指针 (空指针非unique)
- 重载*和->可以像指针一样使用
shared_ptr的模拟实现
采用计数的方式,拷贝一个count++,析构时–count,count等于0时表示是最后一个管理对象,就释放资源
注意:
- 多个智能指针对象管理一个资源
count不能为单个对象私有的,会出现不释放资源的问题,将其定义为static变量貌似可行(不能全局,全局变量有很多缺陷)
- 多个智能指针管理多个资源:
明显static变量就不行,会出现count为负的情况,这里应该使用int* 存储计数
operator=:
对于将管理一个资源的sp对象赋值给另一个对象,应该注意讨论:
例如sp1=sp4:
注意点看注释shared_ptr<T>& operator=(const shared_ptr<T>& sp) //if (*this != sp) //未重载 != 不能比较 //if (this != &sp) //原生指针可以直接比较 if (_ptr != sp._ptr) //原生指针可以直接比较 if (--(*_pCount) == 0)//对于任意条件--(*_pCount)均要执行 //若因为sp1改为管理sp4所管理的资源,若sp1是最后一个管理其原来管理的资源的sp对象,应释放该资源与计数 delete _pCount; delete _ptr; //若剩余其他sp对象管理sp1资源,进行转移管理 _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); return *this;
现代写法:不需要考虑边界情况,同时可以减少不必要的操作(如赋值给自己,先-- 再++)
shared_ptr<T>& operator=(shared_ptr<T> sp) //传值创建一个中间对象sp sp和sp4管理的是同一块资源(=左边this,()里为右边) //例如之前sp4的_pCount为2 sp1的_pCount为1,此时sp的_pCount就为3 (拷贝构造的逻辑) swap(_ptr, sp._ptr); //sp和sp1交换 swap(_pCount, sp._pCount); //sp和sp1交换 return *this; //sp出作用域自己销毁
总代码:
template<class T> class shared_ptr public: shared_ptr(T* ptr) :_ptr(ptr) , _pCount(new int(1)) shared_ptr(shared_ptr<T>& sp) :_ptr(sp._ptr) , _pCount(sp._pCount) ++(*_pCount); shared_ptr<T>& operator=(shared_ptr<T> sp) swap(_ptr, sp._ptr); swap(_pCount, sp._pCount); return *this; ~shared_ptr() if (--(*_pCount) == 0 && _ptr) delete _ptr; delete _pCount; cout << _ptr << endl; T& operator*() return *_ptr; T* operator->() return _ptr; private: T* _ptr; int* _pCount; ;
线程安全
注意:
访问资源的线程安全智能指针管不了,属于使用者管理
智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题智能指针对象拷贝析构的过程中引用计数的线程安全需要保证
(管理同一资源的应该是一把锁,拷贝构造时赋值)
智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的
注意这里的ref(p)右值问题
,参考:C++11 多线程(std::thread)详解
shared_ptr私有成员增加一个
mutex* _pMtx;
在模拟shared_ptr时单独将原子操作分离出来
- 增加计数:
void add_ref() _pMtx->lock(); ++(*_pCount); _pMtx->unlock();
- 减少计数:(删除器见下面)
注意
:需要释放锁资源 (计数为0时,释放资源时)void release_ref() bool flag = false; _pMtx->lock(); if (--(*_pCount) == 0 && _ptr) D del; del(_ptr); // 使用删除器释放即可 //delete _ptr; delete _pCount; flag = true; cout << "释放资源:" << _ptr << endl; _pMtx->unlock(); if (flag == true) delete _pMtx;
更改构造函数和拷贝构造的初始化列表 和 析构函数:
注意
:std库中shared_ptr构造函数加了explicit关键字,防止传参的原生指针隐式类型转换为智能指针explicit shared_ptr(T* ptr = nullptr) :_ptr(ptr) , _pCount(new int(1)) , _pMtx(new mutex) shared_ptr(shared_ptr<T, D>& sp) :_ptr(sp._ptr) , _pCount(sp._pCount) , _pMtx(sp._pMtx) add_ref(); ~shared_ptr() release_ref();
循环引用
例如有一个类:包含一个该类的_prev智能指针一个该类的_next智能指针,主函数创建两个该类的智能指针,同时让他们链接起来:
看到并没有释放资源
当出作用域时b先销毁,countb减为1,a再销毁,counta减为1,均未调用析构函数,而a要销毁的条件是_next销毁,_next指向b,b要销毁的条件是_prev销毁,而_prev指向a,如此形成死循环
,需要weak_ptr解决,见下
lambda+shared_ptr=内存泄漏
参考:Lambda + shared_ptr<> = memory leak
参考:lambda和shared_ptr的内存泄漏
定制删除器(MARK??lambda模板参数????)
std库中shared_ptr在构造函数的时候可以传入删除器
可以这样使用:
模拟实现
:
- std的框架设计底层用一个类专门管理资源计数和释放,所以它可以再构造函数传参,把删除器类型传递给专门管理资源的这个类,但我们是一体化的,只能test::shared_ptr给删除器,析构函数才能拿到删除器
- shared_ptr更改:定义一个默认删除器,作为D缺省值
template<class T> struct DefaultDel void operator()(T* ptr) delete ptr; ; template<class T, class D = DefaultDel<T>>
- 拷贝构造与operator=的参数返回值更改:
shared_ptr(shared_ptr<T, D>& sp) shared_ptr<T, D>& operator=(shared_ptr<T, D> sp)
- release_ref内部更改为删除器删除:
D del; del(_ptr); // 使用删除器释放即可
使用:
weak_ptr
weak_ptr的使用
为解决循环引用问题引入了weak_ptr
- weak_ptr的一个重要用途是通过lock获得this指针的shared_ptr,使对象自己能够生产shared_ptr来管理自己
- weak_ptr只可以从一个shared_ptr或另一个 weak_ptr 对象来构造, 它的构造和析构不会引起引用记数的增加或减少
- 重载了=号:注意右操作数可为shared_ptr
- reset:对象变为空,就像默认构造的一样
- use_count:同其他智能指针,
注意这里不增加引用计数
- expired: 检查是否为空或其所管理的资源是否还有其他shared_ptr在管理
注意:expired的指针在locked时充当空的weak_ptr 对象,因此不能再用于恢复拥有的shared_ptr
weak_ptr不改变其所共享的shared_ptr实例的引用计数,那就可能存在weak_ptr指向的对象被释放掉这种情况,这时就不能使用weak_ptr直接访问对象
- lock:如果未被expired,则返回带有由 weak_ptr 对象保留的信息的 shared_ptr
注意
:
- weak_ptr并未重载*和->,不能像指针一样使用
- weak_ptr在使用前需要检查合法性(调用expired()函数判断是否被destroy)
可以参考
:weak_ptr基本用法以及怎么解决循环引用
如何解决循环引用问题:以循环引用上图为例只需要将a或b的任意一个成员变量改为weak_ptr
weak_ptr的简单模拟
注意:weak_ptr不参与管理资源,是用来弥补shared_ptr的缺陷
先将weak_ptr设为shared_ptr的友元
template<class T> class weak_ptr public: weak_ptr() :_ptr(nullptr) weak_ptr(shared_ptr<T>& sp) :_ptr(sp._ptr) , _pCount(sp._pCount) weak_ptr(weak_ptr<T>& sp) :_ptr(sp._ptr) , _pCount(sp._pCount) weak_ptr<T>& operator=(shared_ptr<T>& sp) _ptr = sp._ptr; _pCount = sp._pCount; return *this; weak_ptr<T>& operator=(weak_ptr<T>& sp) _ptr = sp._ptr; _pCount = sp._pCount; return *this; private: T* _ptr; int* _pCount; ;
以上是关于C++进阶---智能指针的主要内容,如果未能解决你的问题,请参考以下文章