C++智能指针
2022/2/24 1:22:13
本文主要是介绍C++智能指针,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
智能指针
为什么需要智能指针
裸指针存在的问题
裸指针是平常定义的普通指针,他有许多的问题,主要是以下这些:
1.难以区分指向的是单个对象还是一个数组;
2.使用完指针之后无法判断是否应该销毁指针,因为无法判断指针是否“拥有”指向的对象;
3.在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是有其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针;
4.即便已经确定了销毁指针的方法,由于1的原因,仍然无法确定到底是用delete(销毁单个对象)还是
delete[ ] (销毁一个数组);
5.假设上述的问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),有且仅有一次销毁指针操作;任何一条路径遗漏都可能导致内存泄露,而销毁多次则会导致未定义行为;
6.理论上没有方法来分辨一个指针是否处于悬挂状态;
悬挂指针是由于指针的指向内存被删除导致
int *p = nullptr; delete p;
智能指针的工作原理
由一个类来对指针进行封装,在申请一个指针时相当于申请了一个对象,在这个对象的生存期结束时会自动释放指针指向的空间。
智能指针的种类
auto_ptr(c11弃用,c17废除)
class Object { private: int value; public: Object(int x) : value(x) { cout << "Create Object" << this << endl; } ~Object() { cout << "Destory Object" << this << endl; } int &Value() { return value; } const int &Value() const { return value; } }; template <typename _Ty> class my_auto_ptr { private: bool _Owns; _Ty *_Ptr; public: my_auto_ptr(_Ty *p) : _Owns(p != nullptr), _Ptr(p) { } ~my_auto_ptr() { if (_Owns) { delete _Ptr; } _Owns = false; _Ptr = NULL; } _Ty &operator*() const { return *_Ptr; } _Ty *operator->() const { return _Ptr; } void reset(_Ty *p = NULL) { if (_Owns) { delete _Ptr; } _Ptr = p; } _Ty *release() const //释放函数,将该指针的拥有权进行释放 { if (_Owns) { _Owns = false; tmp = _Ptr; _Ptr = nullptr; } return tmp; } }; int main() { my_auto_ptr<Object> obj(new Object(10)); cout << (*obj).Value() << endl; cout << obj->Value() << endl; obj.reset(new Object(20)); }
autoptr的拷贝构造函数,等号运算符重载由于无法确定,导致该类型被弃用。
my_auto_ptr(const my_auto_ptr &p) : _Owns(p._Owns) { if (_Owns) { _Ptr = p; } }
该拷贝构造函数,在进行析构时,会导致一个资源析构两次,引起程序崩溃
my_auto_ptr(const my_auto_ptr &p) : _Owns(p._Owns), _Ptr(release(p)) { }
该拷贝构造函数,移除了原智能指针对对象的拥有权,会导致原指针失效。
同时autoptr无法分辨是一个对象还是一组对象,因此无法正确进行对象的析构。
unique_ptr
唯一拥有权智能指针,删除了类中的拷贝构造函数以及赋值语句,使用c11标准里的移动构造和移动赋值。同时,应用类模板的部分特化可以分辨是一个对象还是一组对象。
#ifndef MY_UNIQUE_PTR_H #define MY_UNIQUE_PTR_H #include <iostream> using namespace std; //删除器 template <class _Ty> class MyDeletor { public: // MyDeletor() = default; MyDeletor() { } void operator()(_Ty *ptr) const { if (ptr != nullptr) { delete ptr; } } }; //删除一组的删除器 template <class _Ty> class MyDeletor<_Ty[]> // 部分特化:模板类的一种特殊情况,当编译器匹配时,会先判断是否满足特化版本 { public: MyDeletor() = default; void operator()(_Ty *ptr) const { if (ptr != nullptr) { delete[] ptr; } } }; // unique_ptr template <typename _Ty, typename _Dx = MyDeletor<_Ty>> class my_unique_ptr { private: _Ty *_Ptr; _Dx _Delete; public: my_unique_ptr(const my_unique_ptr &) = delete; my_unique_ptr &operator=(const my_unique_ptr &) = delete; my_unique_ptr(_Ty *p = nullptr) : _Ptr(p) { cout << "create my_unique_ptr " << this << endl; } my_unique_ptr(my_unique_ptr &&_Y) { _Ptr = _Y._Ptr; _Y._Ptr = nullptr; cout << " move copy my_unique_ptr : " << this << endl; cout << "move create my_unique_ptr" << endl; } ~my_unique_ptr() { cout << "~my_unique_ptr() " << this << endl; if (_Ptr != nullptr) { _Delete(_Ptr); _Ptr = nullptr; } } my_unique_ptr &operator=(my_unique_ptr &&_Y) { if (this = &_Y) { return *this; } reset(_Y.release()); // if (_Ptr != nullptr) { _myDeletor(_Ptr); } //_Ptr = _Y._Ptr; //_Y._Ptr = nullptr; } _Ty &operator*() const { return *_Ptr; } _Ty *operator->() const { return _Ptr; } _Ty *get() const { return _Ptr; } //对bool类型重载 /* 应用场景: if(my_unique_ptr) { } 此时该对象充当bool类型 */ operator bool() const { return !(_Ptr == nullptr); } //将唯一智能指针重新指向,并将之前指向的空间进行释放。 void reset(_Ty *p) { _Ty *old = _Ptr; _Ptr = p; if (old != nullptr) { _Delete(old); } } //释放该智能指针的拥有权 _Ty *release() { _Ty *old = _Ptr; _Ptr = nullptr; return old; } //获取删除器 _Dx &get_deleter() { return _Delete; } const _Dx &get_deleter() const { return _Delete; } void swap(my_unique_ptr _Y) { std::swap(_Ptr, _Y._Ptr); std::swap(_Delete, _Y._myDeletor); } }; //一组对象 部分特化 template <typename _Ty, typename _Dx> //_Dx会继承到泛化中的默认值 class my_unique_ptr<_Ty[], _Dx> { private: _Ty *_Ptr; _Dx _Delete; public: my_unique_ptr(const my_unique_ptr &) = delete; my_unique_ptr &operator=(const my_unique_ptr &) = delete; my_unique_ptr(_Ty *p = nullptr) : _Ptr(p) { cout << "create my_unique_ptr " << this << endl; } my_unique_ptr(my_unique_ptr &&_Y) { _Ptr = _Y._Ptr; _Y._Ptr = nullptr; cout << "move create my_unique_ptr " << this << endl; } ~my_unique_ptr() { cout << "~my_unique_ptr" << this << endl; if (_Ptr != nullptr) { _Delete(_Ptr); _Ptr = nullptr; } } my_unique_ptr &operator=(my_unique_ptr &&_Y) { if (this = &_Y) { return *this; } reset(_Y.release()); // if (_Ptr != nullptr) { _myDeletor(_Ptr); } //_Ptr = _Y._Ptr; //_Y._Ptr = nullptr; } _Ty &operator*() const { return *_Ptr; } _Ty *operator->() const { return _Ptr; } _Ty *get() const { return _Ptr; } //对bool类型重载 /* 应用场景: if(my_unique_ptr) { } 此时该对象充当bool类型 */ operator bool() const { return !(_Ptr == nullptr); } //将唯一智能指针重新指向,并将之前指向的空间进行释放。 void reset(_Ty *p) { _Ty *old = _Ptr; _Ptr = p; if (old != nullptr) { _Delete(old); } } //释放该智能指针的拥有权 _Ty *release() { _Ty *old = _Ptr; _Ptr = nullptr; return old; } //获取删除器 _Dx &get_deleter() { return _Delete; } const _Dx &get_deleter() const { return _Delete; } void swap(my_unique_ptr _Y) { std::swap(_Ptr, _Y._Ptr); std::swap(_Delete, _Y._myDeletor); } _Ty &operator[](size_t _Idx) const { return _Ptr[_Idx]; // } }; #endif
#include "my_uniquer.h" class Object { private: int value; public: Object(int x = 0) : value(x) { cout << "Create Object" << this << endl; } ~Object() { cout << "Destory Object" << this << endl; } }; my_unique_ptr<Object> fun() { return my_unique_ptr<Object>(new Object(10)); } int main() { my_unique_ptr<Object[]> objs = new (Object[2]); }
shared_ptr与weak_ptr
shared_ptr
共享型智能指针,可以让一个实例对象被多个shared_ptr指向,通过指向一个计数器类来实现计数。
结构如下:
_Ptr是用户声明对象的指针类型,指向开辟的空间, _Rep是一个计数器指针指向计数器结构, _Deleter是删除器。
weak_ptr
弱引用智能指针,它主要是为了解决两个共享型智能指针相互引用的问题
如下情况:
class Child; class Parent { public: my_shared_ptr<Child> c; public: Parent() { cout << "create Parent " << endl; } ~Parent() { cout << "destory Parend " << endl; } void hi() const { cout << "hello Parent " << endl; } }; class Child { public: my_shared_ptr<Parent> p; public: Child() { cout << "create Child " << endl; } ~Child() { cout << "destory Child " << endl; } void hi() const { cout << "hello Child " << endl; } };
该结构图如下:
执行以下代码
void fun3() { my_share_ptr<Parent> parent(new Parent()); my_share_ptr<Child> child(new Child()); parent->c = child; child->p = parent; child->p.lock()->hi(); }
此时计数器都为2,当parent智能指针析构时,由于shared_ptr的执行流程为–_Rep->uses,呢么在该对象析构后,引用计数仍为1,所以导致该空间不能正常释放。
如果想要正常释放空间,则需要开发者在该类中对c和p进行空间释放,违背了设计智能指针的初衷。因此为了保证泛化的概念,因此引入weak_ptr,在这种情况下使用weak_ptr来进行相互引用则不会出现该情况。
因为weak_ptr只是增加了_Weak的计数,那么则不会影响对象的正常析构。
#include <iostream> using namespace std; #include <atomic> //原子操作 template <typename _Ty> class MyDeletor { public: MyDeletor() {} void operator()(_Ty *p) { if (p != nullptr) { delete p; p = nullptr; } } }; template <class _Ty> class MyDeletor<_Ty[]> // 部分特化:模板类的一种特殊情况,当编译器匹配时,会先判断是否满足特化版本 { public: MyDeletor() = default; void operator()(_Ty *ptr) const { if (ptr != nullptr) { delete[] ptr; } } }; //共享指针计数器 template <typename _Ty> class RefCnt { public: _Ty *mptr; //指针类型 atomic_int _Uses; //共享指针使用数 atomic_int _Weak; //弱指针使用数 public: RefCnt(_Ty *p = nullptr) : mptr(p), _Uses(0), _Weak(0) { if (p != nullptr) { _Uses = 1; _Weak = 1; } } ~RefCnt() { } }; template <typename _Ty, typename _Dx = MyDeletor<_Ty>> class my_share_ptr { private: _Ty *_Ptr; RefCnt<_Ty> *_Rep; _Dx _mDeletor; public: my_share_ptr(_Ty *_P = nullptr) : _Ptr(nullptr), _Rep(nullptr) { // cout << "my_share_ptr(_Ty *_P = nullptr) " << this << endl; if (_P != nullptr) { _Ptr = _P; _Rep = new RefCnt<_Ty>(_P); } } my_share_ptr(my_share_ptr &_Y) : _Ptr(_Y._Ptr), _Rep(_Y._Rep) { // cout << "my_share_ptr(my_share_ptr &_Y) " << this << endl; if (_Rep != nullptr) { ++_Rep->_Uses; } } my_share_ptr(my_share_ptr &&_Y) : _Ptr(nullptr), _Rep(nullptr) { // cout << "my_share_ptr(my_share_ptr &&_Y)" << endl; _Y.swap(*this); } ~my_share_ptr() { // cout << "~my_share_ptr() " << this << endl; if (_Ptr != nullptr && --_Rep->_Uses == 0) { _mDeletor(_Ptr); //如果引用计数为0则释放掉对象占用的堆空间 if (--_Rep->_Weak == 0) { delete _Rep; //不需要使用删除器,删除器的主要作用在于将智能指针指向的空间进行回收,而在当下结构中,上一步已经回收了。 } } } my_share_ptr &operator=(const my_share_ptr &_Y) { if (this == &_Y || this->_Ptr == _Y._Ptr) { return *this; } if (_Ptr != nullptr && --_Rep->_Uses == 0) { _mDeletor(_Ptr); if (--_Rep->_Weak == 0) { delete _Rep; //不需要使用删除器,删除器的主要作用在于将智能指针指向的空间进行回收,而在当下结构中,上一步已经回收了。 } } _Ptr = _Y._Ptr; _Rep = _Y._Rep; if (_Ptr != nullptr) { ++_Rep->_Uses; } } my_share_ptr &operator=(my_share_ptr &&_Y) { // cout << "my_share_ptr &operator=(my_share_ptr &&_Y)" << endl; if (this == &_Y) { return *this; } if (_Ptr != nullptr && _Y._Ptr != nullptr && _Ptr == _Y._Ptr) //当两个对象不同,但是指向的对象相同时,需要对指向的对象计数进行-1操作 { --_Rep->_Uses; //取消_Y的拥有权 _Y._Ptr = nullptr; _Y._Rep = nullptr; return *this; } if (_Ptr != nullptr && --_Rep->_Uses == 0) { _mDeletor(_Ptr); if (--_Rep->_Weak == 0) { delete _Rep; //不需要使用删除器,删除器的主要作用在于将智能指针指向的空间进行回收,而在当下结构中,上一步已经回收了。 } } _Ptr = _Y._Ptr; _Rep = _Y._Rep; _Y._Ptr = nullptr; _Y._Rep = nullptr; return *this; } inline _Ty *get() const { return _Ptr; } inline _Ty &operator*() const { return *get(); } inline _Ty *operator->() const { return get(); } inline operator bool() const { return _Ptr != nullptr; } inline size_t use_count() { if (_Rep != nullptr) { return _Rep->_Uses; } } void swap(my_share_ptr &s) { std::swap(_Ptr, s._Ptr); std::swap(_Rep, s._Rep); } template <typename _T> //模板参数不能一样 friend class my_weak_ptr; }; template <typename _Ty> class my_weak_ptr { private: RefCnt<_Ty> *_Rep; public: my_weak_ptr() : _Rep(nullptr) { // cout << "my_weak_ptr() " << this << endl; } my_weak_ptr(const my_share_ptr<_Ty> &_Y) : _Rep(_Y._Rep) { // cout << "my_weak_ptr(const my_share_ptr<_Ty> &_Y) : _Rep(_Y._Rep) " << this << endl; if (_Rep != nullptr) { ++_Rep->_Weak; } } my_weak_ptr(const my_weak_ptr &_Y) : _Rep(_Y._Rep) { // cout << "my_weak_ptr(const my_weak_ptr &_Y) " << this << endl; if (_Rep != nullptr) { ++_Rep->_Weak; } } my_weak_ptr(my_weak_ptr &&_Y) : _Rep(_Y._Rep) { // cout << "my_weak_ptr(my_weak_ptr &&_Y) " << this << endl; _Y._Rep = nullptr; } ~my_weak_ptr() { // cout << "~my_weak_ptr() " << this << endl; if (_Rep != nullptr && --_Rep->_Weak == 0) { delete _Rep; } _Rep = nullptr; } my_weak_ptr &operator=(const my_weak_ptr &_Y) { if (this == &_Y && _Rep == _Y._Rep) { return *this; } if (_Rep != nullptr && --_Rep->_Weak == 0) { delete _Rep; } _Rep = _Y._Rep; if (_Rep != nullptr) ++_Rep->_Weak; return *this; } my_weak_ptr &operator=(my_weak_ptr &&_Y) { if (this == &_Y) { return *this; } if (_Rep != nullptr && _Y._Rep != nullptr && _Rep == _Y._Rep) { --_Rep->_Weak; _Y._Rep = nullptr; return *this; } if (_Rep != nullptr && --_Rep->_Weak == 0) { delete (_Rep); } _Rep = _Y._Rep; _Y._Rep = nullptr; return *this; } my_weak_ptr &operator=(my_share_ptr<_Ty> &_Y) { if (_Rep != nullptr && --_Rep->_Weak == 0) { delete _Rep; } _Rep = _Y._Rep; if (_Rep != nullptr) ++_Rep->_Weak; return *this; } my_weak_ptr &operator=(my_share_ptr<_Ty> &&_Y) = delete; bool expired() const { return _Rep->_Uses == 0; } my_share_ptr<_Ty> lock() const { my_share_ptr<_Ty> _Ret; _Ret._Ptr = _Rep->mptr; _Ret._Rep = _Rep; ++_Ret._Rep->_Uses; //必须进行++ 否则当 return _Ret; } }; class Object { private: int value; public: Object(int x = 0) : value(x) { cerr << "Create Object " << this << endl; } ~Object() { cerr << "Destory Object " << this << endl; } int Value() { return value; } void Print() { cerr << value << endl; } }; void fun() { my_share_ptr<Object> sp1(new Object(10)); my_weak_ptr<Object> wp1(sp1); my_weak_ptr<Object> wp2(sp1); } void fun1() { my_weak_ptr<Object> wp1; my_share_ptr<Object> sp1(new Object(10)); wp1 = sp1; if (!wp1.expired()) { wp1.lock()->Print(); // wp1.lock是通过移动构造出来的 } cout << "-------" << endl; } class Child; class Parent { public: my_weak_ptr<Child> c; public: Parent() { cout << "create Parent " << endl; } ~Parent() { cout << "destory Parend " << endl; } void hi() const { cout << "hello Parent " << endl; } }; class Child { public: my_weak_ptr<Parent> p; public: Child() { cout << "create Child " << endl; } ~Child() { cout << "destory Child " << endl; } void hi() const { cout << "hello Child " << endl; } }; void fun3() { my_share_ptr<Parent> parent(new Parent()); my_share_ptr<Child> child(new Child()); parent->c = child; child->p = parent; child->p.lock()->hi(); } int main() { //整个构造过程 // fun(); // fun1(); //弱指针要解决的问题: //类中相互引用,如果不使用weak_ptr呢么对象空间将无法正常释放,需要对析构函数进行修改,加入了weak_ptr主要目的还是为了统一性 // fun3(); }
构造智能指针
一般有两种构造智能指针的方法,一种是上文中的构造方法,直接将对象进行赋值操作,另一种则是通过系统提供的make_shared()或者make_unique(),那这两种方法的区别是什么呢?
make构造与普通方法构造区别
由上文实现方法可以得知,共享指针中的计数器是额外开辟的一个空间,因此当使用普通方法构造时,堆上其实开辟了两次空间,如下图所示:
使用系统内置的make构造共享指针时,只会在堆上开辟一次空间,如下图:
系统会计算好_Rep和对象大小的总空间,一次性在堆上开辟。
make构造的好处
1.一次性开辟空间,减少内存分配的次数
2.另一个好处就是可以增大Cache局部性(Cache Locality):使用make_shared,计数器的内存和原生内存就在堆上排排坐,这样的话我们所有要访问这两个内存的操作就会比另一种方案减少一半的cache misses。
补充:
引入Cache的理论基础是程序局部性原理,包括时间局部性和空间局部性。即最近被CPU访问的数据,短期内CPU还要访问(时间);被CPU访问的数据附近的数据,CPU短期内还要访问(空间)。因此如果将刚刚访问过的数据缓存在Cache中,那下次访问时,可以直接从Cache中取,其速度可以得到数量级的提高。CPU要访问的数据在Cache中有缓存,称为“命中”(Hit),反之则称为“缺失”(Miss)。
3.防止内存泄漏
例如当程序需要考虑执行顺序和异常安全性时,使用普通方法构造可能会导致出现内存泄漏的情况,如下示例
struct 0bject { int i; }; void doSomething(std::shared_ptr<Object> pt,double d); //这个函数可能抛出异常 double couldThrowException( ); int main() { doSomething(std::shared_ptr<0bject>(new 0bject {1024}),couldThrowExcption()) return 0; }
分析上边的代码,在doSomething函数被调用之前至少有三件事情被完成: 构造并给Object分配内存,构造shared ptr以及调用couldThrowException()。C++17中引入了更加严格的鉴别函数参数构造顺序的方法,但是在那之前,上边三件事情的执行顺序应该是这样的:
new Object();
调用couldThrowException()
构造shared_ptr< Object >并管理步骤1开辟的内存。
那么一旦步骤二抛出异常,步骤三就永远都不会发生,因此没有智能指针去管理步骤一开辟的内存也就是内存泄漏了。
make构造的缺点
1.可能会浪费空间
由上文提到,由于make_shared会一次性在堆上开辟好空间,那么同样意味着,他没有办法只释放该空间的某一部分,也就是说,当这个对象被弱引用时,就算该对象被析构了,也不能直接把空间释放掉—还在等计数器的空间释放。因此如果对象占用内存过大,那么这块空间将会在这段时间内被浪费。
2.使用make_shared,首先最可能遇到的问题就是make_shared函数必须能够调用目标类型构造函数或构造方法。然而这个时候即使把 make_shared设成类的友元恐怕都不够用,因为其实目标类型的构造是通过一个辅助函数调用的——不是make_shared这个函数。
要访问的数据在Cache中有缓存,称为“命中”(Hit),反之则称为“缺失”(Miss)。
3.防止内存泄漏
例如当程序需要考虑执行顺序和异常安全性时,使用普通方法构造可能会导致出现内存泄漏的情况,如下示例
struct 0bject { int i; }; void doSomething(std::shared_ptr<Object> pt,double d); //这个函数可能抛出异常 double couldThrowException( ); int main() { doSomething(std::shared_ptr<0bject>(new 0bject {1024}),couldThrowExcption()) return 0; }
分析上边的代码,在doSomething函数被调用之前至少有三件事情被完成: 构造并给Object分配内存,构造shared ptr以及调用couldThrowException()。C++17中引入了更加严格的鉴别函数参数构造顺序的方法,但是在那之前,上边三件事情的执行顺序应该是这样的:
new Object();
调用couldThrowException()
构造shared_ptr< Object >并管理步骤1开辟的内存。
那么一旦步骤二抛出异常,步骤三就永远都不会发生,因此没有智能指针去管理步骤一开辟的内存也就是内存泄漏了。
make构造的缺点
1.可能会浪费空间
由上文提到,由于make_shared会一次性在堆上开辟好空间,那么同样意味着,他没有办法只释放该空间的某一部分,也就是说,当这个对象被弱引用时,就算该对象被析构了,也不能直接把空间释放掉—还在等计数器的空间释放。因此如果对象占用内存过大,那么这块空间将会在这段时间内被浪费。
2.使用make_shared,首先最可能遇到的问题就是make_shared函数必须能够调用目标类型构造函数或构造方法。然而这个时候即使把 make_shared设成类的友元恐怕都不够用,因为其实目标类型的构造是通过一个辅助函数调用的——不是make_shared这个函数。
这篇关于C++智能指针的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-09flutter3.x_macos桌面os实战
- 2024-05-09Rust中的并发性:Sync 和 Send Traits
- 2024-05-08使用Ollama和OpenWebUI在CPU上玩转Meta Llama3-8B
- 2024-05-08完工标准(DoD)与验收条件(AC)究竟有什么不同?
- 2024-05-084万 star 的 NocoDB 在 sealos 上一键起,轻松把数据库编程智能表格
- 2024-05-08Mac 版Stable Diffusion WebUI的安装
- 2024-05-08解锁CodeGeeX智能问答中3项独有的隐藏技能
- 2024-05-08RAG算法优化+新增代码仓库支持,CodeGeeX的@repo功能效果提升
- 2024-05-08代码报错不用愁,CodeGeeX一键完成代码修复、错误解释的功能上线了!
- 2024-05-08今天开始程序员不用再发愁写commit message了,全部由CodeGeeX自动完成!