当前位置: 首页 > news >正文

智能指针:C++内存管理的现代解决方案

目录

 一、引言:为什么需要智能指针?

二、智能指针是什么

1)unique_ptr

2)shared_ptr 

3)shared_ptr的循环引用

4)weak_ptr 

三、结语 


 一、引言:为什么需要智能指针?

我们来看下面的例子~


void func() {int* p1 = new int;int* p2 = new int;//此处省略一万行代码......// 抛了个异常//后续代码直接被跳过,导致内存泄漏delete p1;delete p2;
}
int main()
{try {func();}catch (...) {std::cout << "捕获到异常!" << std::endl;}//继续向下执行return 0;
}

在上面的场景中,导致了非常严重的问题——内存泄漏。可以看到,我们可不是忘了释放资源哈。其中一个解决方案是:在捕获异常的catch块里手动释放p1,p2,但是一个两个还好,如果多了会导致代码的臃肿,不够优雅。现代的解决方案就是使用智能指针。

二、智能指针是什么

如果要用一句话来定义智能指针的话,就是:智能指针 = 对象化指针 + 自动生命周期管理。智能指针它实际上就是一个封装了原始指针的类模板,然后通过RAII机制来确保内存安全。

RAII:核心就是将资源的生命周期与对象的生命周期进行绑定,确保对象在离开作用域时能够自动释放其资源。

下面将围绕C++11引入的unique_ptr、shared_ptr、weak_ptr进行介绍,上古时代的auto_ptr这里就不展开叙述了,因为它的一些缺陷导致在项目中很少用到,甚至禁用。

1)unique_ptr

unique_ptr最突出的特点就是:一个unique_ptr,独占它所指向的对象。它不支持拷贝,也不支持赋值。但是,万事无绝对,当unique_ptr作为函数返回值的时候是允许的。估计是因为作为返回值时,它是个将亡值,进行了移动构造,管理权转移。

//error
std::unique_ptr<int> p1(new int(1));
std::unique_ptr<int> p2(p1);//true
std::unique_ptr<int> func(int num) {return std::unique_ptr<int>(new int(num));
}

2)shared_ptr 

shared_ptr和unique_ptr正好相反,shared_ptr它是允许拷贝和赋值的,它所指向的对象是共享的,而不是单独占有。shared_ptr它是通过引用计数来管理对象的生命周期的,当引用计数为0时,对象被自动释放。下面实现一个简单的shared_ptr类,主要是理解它的核心功能,跟库里面的实现当然没法比~

namespace pcz {template <typename T>class shared_ptr {public:shared_ptr(T* ptr = nullptr) :_ptr(ptr),_ref_count(new int(1)){}shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr),_ref_count(sp._ref_count) {(*_ref_count)++;}shared_ptr<T>& opeartor = (const shared_ptr<T> &sp){if (sp._ptr  != _ptr) {release();_ptr = sp._ptr;_ref_count = sp._ref_count;(*_ref_count)++;}}~shared_ptr() {release();}private:void release() {if (--(*_ref_count) == 0) {delete _ptr;delete _ref_count;}}private:T* _ptr;		  //原始指针int* _ref_count;  //引用计数};
}

1)引用计数不能写成static成员变量。

方框表示new出来的两个对象,这两个对象的类型是一样的。如果shared_ptr中的引用计数采用的是static成员变量,由于静态成员变量属于整个类,也就是说属于这个类的所有对象。这时,在new出右边的对象后,引用计数为3,这显然是不对的。 

2)在实现拷贝赋值的时候,一定要减减原指针指向对象的引用计数在让它指向新的对象,否则会导致内存泄漏。

此时p2指向了右边的对象,但是左边的对象的引用计数仍为2,p1析构后,引用计数为1,导致内存泄漏。

3) 在实现拷贝赋值的时候,上述的实现方法是上来就减减引用计数,但是如果该对象的引用计数为1,减减后就立马被释放掉了,后续执行的sp._ptr等就是是对空指针的解引用,导致程序崩溃。所以需要做一次判断,是不是自己给自己赋值。

3)shared_ptr的循环引用

在一些场景下,如果构成循环引用,那么会导致内存泄漏。下面举个例子~

struct ListNode {ListNode(){}std::shared_ptr<ListNode> _prev;std::shared_ptr<ListNode> _next;~ListNode() {std::cout << "~ListNode()" << std::endl;}
};
int main()
{std::shared_ptr<ListNode> p1(new ListNode);std::shared_ptr<ListNode> p2(new ListNode);p1->_next = p2;p2->_prev = p1;return 0;
}

可以看到,new了两个对象,但是一次析构都没有调用,妥妥的内存泄漏。

可以看到,这个释放逻辑成环了,因而导致内存泄漏了。如果打破这种循环?答案是:weak_ptr。

4)weak_ptr 

weak_ptr就是为解决shared_ptr的循环引用问题而生的。weak_ptr是一种不控制所指向对象生命周期的智能指针,它指向一个shared_ptr管理的对象,但是不增加引用计数。下面将上述代码内部的shared_ptr改为weak_ptr,并看看运行结果。

struct ListNode {ListNode(){}std::weak_ptr<ListNode> _prev;std::weak_ptr<ListNode> _next;~ListNode() {std::cout << "~ListNode()" << std::endl;}
};
int main()
{std::shared_ptr<ListNode> p1(new ListNode);std::shared_ptr<ListNode> p2(new ListNode);p1->_next = p2;p2->_prev = p1;return 0;
}

三、结语 

 其实,智能智能类还有很多成员函数,这里就不一一介绍了,需要使用的时候可以查阅文档。还需提醒的是,智能指针并非那么智能,它有时候也是经不起我们乱来的,总之,规范使用能够帮助我们避免很多不必要的麻烦~


感谢支持~

相关文章:

  • clangd与clang-tidy
  • Ansible模块——对被控主机检查是否可达和执行Shell命令
  • 电池的寿命(不清楚是什么类型/虽然有标明是贪心)
  • DAX 权威指南1:DAX计算、表函数与计算上下文
  • JS DOM操作与事件处理从入门到实践
  • Flink和Spark的选型
  • 【vue】vuex实现组件间数据共享 vuex模块化编码 网络请求
  • 使用 Jackson 在 Java 中解析和生成 JSON
  • C.printf 函数基础
  • 大模型的RAG技术系列(三)
  • linux和linux 、linux和windows实现文件复制笔记
  • 基于ssm+mysql的快递管理系统(含LW+PPT+源码+系统演示视频+安装说明)
  • C语言复习--柔性数组
  • Vite Proxy配置详解:从入门到实战应用
  • Activity动态切换Fragment
  • 养生:为健康生活添彩
  • 【Linux第三章】vim
  • 达索PLM系统是什么?有什么用?
  • 亿级流量系统架构设计与实战(六)
  • 【MySQL】事务(重点)
  • 体坛联播|穆勒主场完成拜仁谢幕战,山西车队再登环塔拉力赛
  • 上海劳模风采馆焕新升级后重新开放,展示480位劳模先进故事
  • 广西钦州:坚决拥护自治区党委对钟恒钦进行审查调查的决定
  • 4月证券私募产品备案量创23个月新高,股票策略占比超六成
  • 习近平会见缅甸领导人敏昂莱
  • 河南省平顶山市副市长许红兵主动投案,接受审查调查