当前位置: 首页 > 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;
}

三、结语 

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


感谢支持~

http://www.xdnf.cn/news/383527.html

相关文章:

  • 专业级软件卸载工具:免费使用,彻底卸载无残留!
  • 【CF】Day56——Codeforces Round 940 (Div. 2) and CodeCraft-23 BCD
  • 警备,TRO风向预警,In-N-Out Burgers维权风暴来袭
  • 25.K个一组翻转链表
  • 2025年PMP 学习七 -第5章 项目范围管理 (5.4,5.5,5.6 )
  • 多线程获取VI模块的YUV数据
  • 21、DeepSeekMath论文笔记(GRPO)
  • 十七、统一建模语言 UML
  • Win11安装APK方法详解
  • Trex -用 Python生成特定的流量模式
  • C++:this指针
  • CMake 入门实践
  • 牛客练习赛138
  • 8.5 表格进阶
  • (四)毛子整洁架构(Presentation层/Authentiacation)
  • 批量修改json文件中的标签
  • 【MCAL】TC397+EB-tresos之I2c配置实战(同步、异步)
  • 2025年客运从业资格证备考单选练习题
  • Wallcraft 3.53.0 | 提供高质量动态4D壁纸,解锁高级版,无广告干扰
  • 《Python星球日记》 第50天:深度学习概述与环境搭建
  • 数据治理框架在企业中的落地:从理念到实践
  • OSPF案例
  • 完整进行一次共线性分析
  • Java代理
  • Android开发-图像显示
  • 如何通过合法数据变现实现收入增长
  • LVGL对象的盒子模型和样式
  • Arduino 开源按键库大合集(单击/双击/长按实现)
  • VB与Excel无缝连接实现指南
  • 编译后的js文件如何跟进调试