C++_智能指针详解

什么是智能指针?为什么要有智能指针?到目前为止,我们编写的程序所使用的对象都有着严格定义的生命周期。比如说,全局对象在程序启动时分配,在程序结束时销毁;再比如说局部static对象在第一次使用前分配,在程序结束时销毁......除了这些对象,C++还支持动态分配对象。动态分配的对象的生命周期与它们在哪里创建无关,只有当显式地被释放时,这些对象才会被销毁

如果不显式地释放,则很有可能造成内存泄漏!!而动态对象的正确释放是编程中最容易出错的地方,所以C++引入了智能指针的概念,来帮助我们编程人员更好的释放。

了解内存泄漏与其危害

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死!

智能指针的使用及原理

RAII

RAII(Resource Acquisition Is Initialization,资源获得即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构时释放资源。因此,我们实际上就是把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显式地释放资源(因为析构函数是自动调用的)。
  • 采用这种方式,对象所需的资源在其生命周期内始终保持有效
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr)        :_ptr(ptr)             // 把需要管理的对象托管在成员变量中{}~SmartPtr(){if(_ptr)delete _ptr;       // 当该类析构的时候,自动清理资源}
private:T* _ptr;
};
double Division()
{int a, b;cin >> a >> b;if (b == 0)                // 当b == 0时抛出异常throw "Division by zero condition!";return (double)a / (double)b;
}
void Func()
{ShardPtr<int> sp1(new int);        // 动态创建对象ShardPtr<int> sp2(new int);cout << Division() << endl;
}
int main()
{try Func();catch(const exception& e)cout<<e.what()<<endl;return 0;
}

智能指针的原理

上面代码中的SmartPtr类可以说是智能指针吗?不,还不是!既然叫做指针,那么必须要具有指针的各种行为。比如说:指针可以解引用,也可以通过->去访问所指空间中的内容,因此,还应在SmartPtr类中添加类似以下功能的代码:

T& operator*() 
{return *_ptr;
}
T* operator->() 
{return _ptr;
}

总而言之,智能指针的原理应包括以下两点:

  • RAII特性。
  • 重载operator*和opertaor->,具有像指针一样的行为。

 std::auto_ptr

在C++98版本的库中提供了auto_ptr的智能指针。auto_ptr在实现原理上使用了管理权转移的思想。

// Date为日期类
auto_ptr<Date> ap1(new Date);
auto_ptr<Date> ap2(ap1);        // 拷贝,管理权转移
// ap1的所有权都给了ap2,所以ap1现在什么都没有
// 访问ap1的任何内容都会报错
ap1->_year++;                   // 报错

auto_ptr<Date> ap2(ap1)构造拷贝出ap2,ap2完全夺取了ap1的管理权,进而导致ap1无家可归,进行 ap1->访问时程序就会报错。同样道理,当进行了ap2 = ap1,程序也存在这样的问题,原因依旧在于ap1被彻彻底底的夺走了一切,所以这种编程思想是十分危险的。总之,auto_ptr是一个失败设计,很多公司明确要求禁止使用auto_ptr。
 

 std::unique_ptr

unique_ptr的实现原理很简单,就是简单粗暴的防拷贝。unique_ptr类中的拷贝和赋值都被禁掉了。

template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*()    {return *_ptr;}T* operator->()    {return _ptr;}// 被禁掉的拷贝构造和赋值unique_ptr(const unique_ptr<T>& sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:T* _ptr;
};

上面的unique_ptr用于处理单个的对象,而new/delete和new[]/delete[]底层实现机制又是不同的,对于new[]出来的多个对象unique_ptr又给出了特化的版本,请点击unique_ptr文档

// Date为日期类
// 处理单个对象
unique_ptr<Date> up1(new Date);
// 处理多个对象
unique_ptr<Date[]> up2(new Date[5]);

std::shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

看着上面的理论吧啦吧啦的,我个人感觉不是很好理解,结合图示和模拟实现的代码可能好理解一点,下图是拷贝的过程原理,赋值类似。

// 基础版的shared_ptr模拟实现
template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr):_ptr(ptr), _pcount(new int(1))	// 这里新开一块空间,用作计数{}// s2(s1)shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount){++(*_pcount);}// sp3 = sp1shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();			// 清理sp3之前指向的空间_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}void release(){if (--(*_pcount) == 0){delete _ptr;delete _pcount;_ptr = nullptr;_pcount = nullptr;}}~shared_ptr(){release();}// 相应的指针行为 T* get()           {return _ptr;}int use_count()    {return *_pcount;}T& operator*()     {return *_ptr;}T* operator->()    {return _ptr;}
private:T* _ptr = nullptr;int* _pcount;
};

注:shared_ptr不需要显式的实现移动构造和移动赋值。

循环引用

尽管shared_ptr是一种比较完美的编程思想,但是再完美也会有一定的瑕疵,而这个瑕疵-就是循环引用。一般情况下,循环引用是不会轻易遇到的,如果遇到了,那你就自认倒霉叭~~~

我们先来认识一下循环引用,请看代码

// 循环引用的场景
struct ListNode
{int _data;shared_ptr<ListNode> _prev;shared_ptr<ListNode> _next;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{shared_ptr<ListNode> n1(new ListNode);shared_ptr<ListNode> n2(new ListNode);n1->_next = n2;    // n1指向n2,语句1n2->_prev = n1;    // n2指向n1,语句2return 0;//输出结果为空
}

上述代码中,出现循环引用的主要原因就是语句1与语句2同时存在。这两行代码存在其中的任何一行都不会有问题,怕的就是两行代码同时存在!!!它们同时存在会构成循环引用,循环引用会导致内存泄漏。

为了解决上述情况,C++11引入了weak_ptr,weak_ptr不同于上述智能指针,它不支持管理资源,只是与shared_ptr配合使用。严格来说,weak_ptr不是智能指针,因为它不支持RAII。只要将上述代码中的ListNode类中的shared_ptr改为weak_ptr,循环引用就可以得到很好的解决:

struct ListNode
{int _data;// 改为weak_ptrweak_ptr<ListNode> _prev;weak_ptr<ListNode> _next;~ListNode(){cout << "~ListNode()" << endl;}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/439249.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

electron-builder 首次执行报错问题解决

假日想研究一下 react electron 的使用&#xff0c;结果发现首次打包疯狂报错&#xff0c;研究了一下之后才发现是第一次的话 electron-builder 会从外面下载依赖包到我们系统中&#xff0c;由于某种力量导致压缩包无法下载或者是下载过慢导致失败&#xff0c;要解决其实也简单…

初学51单片机之I2C总线与E2PROM二

总结下上篇博文的结论&#xff1a; 1&#xff1a;ACK信号在SCL为高电平期间会一直保持。 2&#xff1a;在字节数据传输过程中如果发送电平跳变&#xff0c;那么电平信号就会变成重复起始或者结束的信号。&#xff08;上篇博文的测试方法还是不能够明确证明这个结论&#xff0…

【C++】入门基础介绍(上)C++的发展历史与命名空间

文章目录 1. 前言2. C发展历史2. 1 C版本更新特性一览2. 2 关于C23的一个小故事: 3. C的重要性3. 1 编程语言排行榜3. 2 C在工作领域中的应用 4. C学习建议和书籍推荐4. 1 C学习难度4. 2 学习书籍推荐 5. C的第一个程序6. 命名空间6. 1 namespace的价值6. 2 namespace的定义6. …

首届中美可持续发展峰会在加州圆满举行,引领国际绿色发展新方向

现场嘉宾与(部分)与会人员大合影 2024年8月18日,由美国领创商业联盟(Youth Entrepreneur Business Alliance, YEBA)主办的首届中美可持续发展峰会(Sino-American Symposium on Sustainable Development)在加州森林湖市(Lake Forest)盛大举行。此次峰会吸引了数百名来自中美两国…

HTML+CSS之表格(15个案例+代码+效果图+素材)

目录 1.table标签的border属性 案例:制作一个带边框的表格 1.代码 2.效果 2.table标签的cellspacing属性 案例:制作一个带边距的表格 1.代码 2.效果 3.table标签的cellpadding属性 1.代码 2.效果 4.table标签的width和height属性 案例:指定宽高的表格 1.代码 2.效果 5.table标签…

全新芒果YOLOv10改进135:最新注意力机制EMA:即插即用,具有跨空间学习的高效多尺度注意力模块,ICCASSP 2023

💡本篇内容:芒果YOLOv10改进135:最新注意力机制EMA:即插即用,具有跨空间学习的高效多尺度注意力模块,ICCASSP 2023 **具有跨空间学习的高效多尺度注意力模块EMA | 即插即用 该模块通常包括多个并行的注意力子模块,每个子模块关注于输入数据的不同尺度或分辨率。这些子模块…

HTML+CSS表单控件(11个案例+代码+效果图)

目录 单行文本框 (text) 案例:制作一个单行文本框 1.代码 2.效果 密码输入框 (password) 案例:制作密码输入框 1.代码 2.效果 单选按钮 (radio) 案例:制作单选按钮 1.代码 2.效果 复选框 (checkbox) 案例:制作一个复选框 1.代码 2.效果 普通按钮 (button) 案例:制作一个普通按钮…

Java毕业设计实战项目之基于SSM框架的民宿预定系统

项目技术架构&#xff1a; 该SSMVue的民宿预定系统&#xff0c;后端采用SSM架构&#xff0c;前端采用VueElementUI实现页面的快速开发&#xff0c;并使用关系型数据库MySQL存储系统运行数据。本系统分为三种角色&#xff0c;分别是系统管理员&#xff0c;用户&#xff0c;房主…

RD-Agent Windows安装教程

RD-Agent Windows安装教程 QuantML QuantML 2024年09月23日 18:30 Content RD-Agent 是微软亚洲研究院推出的一款自动化研究与开发工具&#xff0c;能够通过LLMs自动构建因子和策略&#xff0c;相关介绍见我们之前的文章&#xff1a;RD-Agent &#xff1a;自动化Quant工厂 然…

10.5二分专练,二分边界情况,+1不加1的判断,方向判断,各种DEBUG

5 https://leetcode.cn/problems/minimum-speed-to-arrive-on-time/submissions/570242512/ 就是说总时间是 前n-1量汽车的运行时间&#xff0c;向上取整&#xff0c;然后再加上最后一辆列车的运行时间 最快的话是需要n-1个小时 搜索空间就是时速&#xff0c;左边界是1&#x…

windows中下载、安装、配置JDK/JDK环境配置/Java配置环境变量/Linux中安装配置JDK环境

JDK下载(官网)、安装、配置(包括系统、idea、eclipse)一篇就够了 1、问题概述? Java开发者必须掌握的JDK下载、安装、配置过程。 包括在Eclipse及IDEA中的配置使用 2、下载JDK 【注册Oracle官网账号】 下载的前天是注册orcle官网账号,作为开发者,这个必须有,随时关注…

VBA信息获取与处理第三个专题第三节:工作薄在空闲后自动关闭

《VBA信息获取与处理》教程(版权10178984)是我推出第六套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。这部教程给大家讲解的内容有&#xff1a;跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互…

Web安全 - 路径穿越(Path Traversal)

文章目录 OWASP 2023 TOP 10导图定义路径穿越的原理常见攻击目标防御措施输入验证和清理避免直接拼接用户输入最小化权限日志监控 ExampleCode漏洞代码&#xff1a;路径穿越攻击案例漏洞说明修复后的安全代码代码分析 其他不同文件系统下的路径穿越特性Windows系统类Unix系统&a…

记录|Modbus-TCP产品使用记录【摩通传动】

目录 前言一、摩通传动实验图1.1 配置软件 IO_Studio1.2 测试软件Modbus Poll1.2.1 读写设置测试1.2.2 AI信号的读取 1.3 对应的C#连接Modbus的测试代码如下【自制&#xff0c;仅供参考】1.4 最终实验图 更新时间 前言 参考文章&#xff1a; 自己需要了解和对比某些产品的Modbu…

【MySQL】服务器管理与配置

MySQL服务器 服务器默认配置 查看服务器默认选项和系统变量 mysqld --verbose --help 查看运行时的系统变量&#xff0c;可以通过like去指定自己要查询的内容 状态变量的查看 系统变量和状态变量的作用域 全局作用域&#xff1a; 对于每个会话都会生效当前会话&#xff1a;只…

初识算法 · 滑动窗口(1)

目录 前言&#xff1a; 长度最小的子数组 题目解析 算法原理 算法编写 无重复长度的最小字符串 题目解析 算法原理 算法编写 前言&#xff1a; 本文开始&#xff0c;介绍的是滑动窗口算法类型的题目&#xff0c;滑动窗口本质上其实也是双指针&#xff0c;但是呢&#…

异常处理【C++提升】(基本思想,重要概念,异常处理的函数机制、异常机制,栈解旋......你想要的全都有)

更多精彩内容..... &#x1f389;❤️播主の主页✨&#x1f618; Stark、-CSDN博客 本文所在专栏&#xff1a; C系列语法知识_Stark、的博客-CSDN博客 座右铭&#xff1a;梦想是一盏明灯&#xff0c;照亮我们前行的路&#xff0c;无论风雨多大&#xff0c;我们都要坚持不懈。 异…

828华为云征文|华为云Flexus云服务器X实例搭建部署H5美妆护肤分销商城、前端uniapp

准备国庆之际&#xff0c;客户要搭个 H5 商城系统&#xff0c;这系统好不容易开发好啦&#xff0c;就差选个合适的服务器上线。那可真是挑花了眼&#xff0c;不知道哪款性价比高呀&#xff01;就像在琳琅满目的选择前。最终慧眼识珠&#xff0c;选择了华为云 Flexus X。至于为什…

redis高级篇 抢红包案例的设计以及分布式锁

一 抢红包案例 1.1 抢红包 二倍均值算法&#xff1a; M为剩余金额&#xff1b;N为剩余人数&#xff0c;公式如下&#xff1a; 每次抢到金额随机区间&#xff08;0&#xff0c;&#xff08;M/N&#xff09;*2&#xff09; 这个公式&#xff0c;保证了每次获取的金额平均值…

TX-LCN框架 分布式事务

一、三种事务模式 1&#xff09;LCN 基于XA协议&#xff0c;事务提交或回滚的操作由事务管理服务器统一告诉它管理的多个项目&#xff0c;也就是说在A事务&#xff0c;B事务的事务提交操作或回滚操作都是在同一时刻发生&#xff0c;并且要么都提交&#xff0c;要么都回滚。 LCN…