【C++学习】C++智能指针:提高代码安全与性能的利器

文章标题

  • 智能指针的提出
  • 智能指针概念及使用
    • RAII
  • 智能指针的原理
  • C++库多种智能指针详解
    • 版本一:std::auto_ptr(C++98)
      • 1. std::auto_ptr 使用
      • 2. std::auto_ptr 原理
      • 3. std::auto_ptr 模拟实现
    • 版本二:unique_ptr (C++11)
      • 1. unique_ptr 的使用
      • 2. unique_ptr 的原理
      • 3. unique_ptr 的模拟实现
    • 版本三: std::shared_ptr(C++11)
  • 循环引用问题
      • weak_ptr的简单实现


智能指针的提出

在上篇所将的C++异常的文章中,有一个这样的场景:在new/malloc一个空间到delete/free释放这段资源之间,抛异常了,抛异常后直接跳到了catch语句,导致后面的delete/free语句没有执行到,会导致内存泄漏问题。
如下面这个场景:

  • 如下代码,当main函数调用func函数时,func函数内部给ptr开辟了一个空间,然后本来是要在func函数结束时将ptr空间释放的,但是中间如果抛了异常,接直接跳到了catch语句,导致ptr空间没有被释放,造成内存泄漏。
void func(){int* ptr = new int(2);   throw("异常");delete ptr;
}
int main()
{try {func();}catch (const char* s){cout << s << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}

如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:
    这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条 智能指针 来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。

智能指针概念及使用

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。(获取到一个资源后,拿去初始化一个对象,并且使用完将其释放)。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

例如下面的例子:

//使用RAII思想设计的Smart_Pointer类
//将开好的资源给Smart_Pointer这个类创建一个对象,该对象里
//面是一个指针,这个对象来管理这个资源,当对象出了作用域,
//自动调用析构函数将资源清理释放,这样就不会有忘记释放资源
//或则上面中间异常导致delete不到的问题。
template<class T>
class Smart_Pointer{      
public:Smart_Pointer(T* ptr):_ptr(ptr){}~Smart_Pointer(){cout << "~Smart_Pointer" << endl;delete _ptr;}
private:T* _ptr;
};void func()
{Smart_Pointer<int> ptr(new int);   throw("异常");
}
int main(){try {func();}catch (const char* s){cout << s << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}

智能指针的原理

上述的SmartPointer 还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:SmartPointer模板类中还得需要重载 * 、->,才可让其像指针一样去使用。

举个例子:

template<class T>
class Smart_Pointer
{
public:Smart_Pointer(T* ptr):_ptr(ptr){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~Smart_Pointer(){cout << "~Smart_Pointer" << endl;delete _ptr;}
private:T* _ptr;
};
struct Date{Date(int year, int month, int day):_year(year),_month(month),_day(day){}~Date(){_year = _month = _day = 0;}int _year;int _month;int _day;
};
int main(){Smart_Pointer<int> ptr1(new int(1));cout << *ptr1 << endl;Smart_Pointer<Date> ptr2(new Date(2024, 4, 10));cout << ptr2->_year <<" " << ptr2->_month << " " << ptr2->_day << " " << endl;return 0;
}
//运行结果:
//1
//2024 4 10
//~Smart_Pointer
//~Smart_Pointer

但是,在实际中,我们可能将指针指向另一个空间,或指针间的拷贝,赋值等问题。
那么,智能指针的拷贝是需要深拷贝还是浅拷贝呢?

答案是:浅拷贝,因为智能指针的行为是模拟指针的行为。
之前学习的数据结构:比如list,vector等容器,特点是,利用这些资源存储管理数据,资源是自己的,拷贝时希望资源各自一份,不是同时利用同一份资源。
而智能指针与迭代器类似,资源不是自己的,代管资源,拷贝的时候希望直指向同一块资源;

那么智能指针的拷贝赋值等问题怎么解决呢?那我们就来看看C++中智能指针的历史发展。


C++库多种智能指针详解

  • 智能指针发展历史
  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

版本一:std::auto_ptr(C++98)

文档链接:https://legacy.cplusplus.com/reference/memory/auto_ptr/?kw=auto_ptr

1. std::auto_ptr 使用

//包头文件memory
#include <memory>
int main()
{auto_ptr<int> ptr1(new int(10));auto_ptr<int> ptr2(new int(30));ptr1 = ptr2;   cout<<*ptr1<<endl;   //30
}

2. std::auto_ptr 原理

//当我们打印ptr1的内容时,运行报错
int main()
{auto_ptr<int> ptr1(new int(10));auto_ptr<int> ptr2(ptr1);cout << *ptr2 << endl;   //10//cout << *ptr1 << endl;    //会报错
}

在这里插入图片描述

运行调试时发现:

  • ptr2拷贝构造ptr1前:
    在这里插入图片描述

  • ptr2拷贝构造ptr1后
    在这里插入图片描述

  • 原理总结:
    根据调试结果可以分析出:
    std::auto_ptr是将被拷贝对象值拷贝给拷贝对象,然后将被拷贝对象置空处理;

3. std::auto_ptr 模拟实现

C++98版本的库中就提供了auto_ptr的智能指针。
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份。

template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& p){_ptr = p._ptr;p._ptr = nullptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~auto_ptr(){cout << "~Smart_Pointer" << endl;delete _ptr;}
private:T* _ptr;
};

版本二:unique_ptr (C++11)

文档链接https://legacy.cplusplus.com/reference/memory/unique_ptr/?kw=unique_ptr
unique_ptr 版本的智能指针解决拷贝问题,简单粗暴,直接禁止拷贝和赋值。

1. unique_ptr 的使用

//包头文件memory
#include <memory>
int main()
{unique_ptr <int> ptr1(new int(10));//unique_ptr <int> ptr2(ptr1);   //编译报错cout << *ptr2 << endl;//cout << *ptr1 << endl;   
}

2. unique_ptr 的原理

原理:将unipue_ptr里面的拷贝构造与赋值运算符直接禁掉;

3. unique_ptr 的模拟实现

template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr):_ptr(ptr){}unique_ptr(unique_ptr<T>& p) = delete;unique_ptr<T>& operator=(unique_ptr<T>& p) = delete;T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~unique_ptr(){cout << "~unique_ptr()" << endl;delete _ptr;}
private:T* _ptr;
};

版本三: std::shared_ptr(C++11)

文档链接https://legacy.cplusplus.com/reference/memory/shared_ptr/?kw=unique_ptr
shared_ptr 允许拷贝。

  1. shared_ptr使用
int main()
{shared_ptr<int> ptr1(new int(10));shared_ptr<int> ptr2(ptr1);   //可拷贝构造shared_ptr<int> ptr3;ptr3 = ptr1;    //可赋值cout << *ptr2 << endl;cout << *ptr1 << endl;cout << *ptr3 << endl;
}
  1. shared_ptr原理

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

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

注意:share_ptr不允许隐式类型转换

思考:为什么不能用静态成员变量来计数?
如图所示:
在这里插入图片描述
解析
当使用静态成员变量来计数,如图所示,开始时ptr1指向的一个空间,当用ptr1拷贝构造或则复制拷贝一个对象(ptr2)时,只需要_count++,当他们中的一个对象析构时,只需要_count–,当_count等于1时,说明只有一个对象管控着这个资源,当这个对象析构的时候,释放这块空间;

用静态成员变量虽然可以解决这种场景,但是当重新构造另一个对象的时候,该对象引用计数应该为1,但是这里却不为一。
所以用静态成员变量计数行不通。

解决方法:
对象里面存储一个int*的指针,构造的时候将这个空间的值初始化为1,当拷贝构造或者析构的时候,只需要++,–即可;
如图:
在这里插入图片描述

  1. shared_ptr模拟实现
template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr=nullptr):_ptr(ptr),_count(new int(1)){}//拷贝构造shared_ptr(shared_ptr<T>& p){assert(_ptr != p._ptr);_ptr = p._ptr;_count = p._count;++(*_count);}//ptr1=ptr2shared_ptr<T>& operator=(shared_ptr<T>& p){//判断自己赋值给自己,或则间接自己给自己赋值if (_ptr != p._ptr){if (--(*_count) == 0){delete _ptr;delete _count;}_ptr = p._ptr;_count = p._count;++(*_count);}return *this;}int getconut(){return *(_count);}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~shared_ptr(){cout << "~shared_ptr()" << endl;//当引用计数--后为0,则释放if (--(*_count)==0){delete _ptr;delete _count;}}
private:T* _ptr;int* _count;
};

循环引用问题

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

在这里插入图片描述
解决方案:
在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
原理就是,node1->_next = node2;和node2->_prev = node1;
时weak_ptr的_next和_prev不会增加node1和node2的引用计数。

weak_ptr的简单实现

代码实现:

template<class T>
class weak_ptr
{
public:weak_ptr():_ptr(nullptr){}weak_ptr(shared_ptr<T>& p){_ptr = p.get();}shared_ptr<T>& operator=(shared_ptr<T>&  p){_ptr = p.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~weak_ptr(){cout << "~weak_ptr()" << endl;delete _ptr;	}private:T* _ptr;};

本章完~

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

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

相关文章

数码相框-显示JPG图片

LCD控制器会将LCD上的屏幕数据映射在相应的显存位置上。 通过libjpeg把jpg图片解压出来RGB原始数据。 libjpeg是使用c语言实现的读写jpeg文件的库。 使用libjpeg的应用程序是以"scanline"为单位进行图像处理的。 libjpeg解压图片的步骤&#xff1a; libjpeg的使…

2023年MathorCup数学建模D题航空安全风险分析和飞行技术评估问题解题全过程文档加程序

2023年第十三届MathorCup高校数学建模挑战赛 D题 航空安全风险分析和飞行技术评估问题 原题再现 飞行安全是民航运输业赖以生存和发展的基础。随着我国民航业的快速发展&#xff0c;针对飞行安全问题的研究显得越来越重要。2022 年 3 月 21 日&#xff0c;“3.21”空难的发生…

STM32之HAL开发——FatFs文件系统移植

FatFs文件系统移植 FatFs 程序结构图 移植 FatFs 之前我们先通过 FatFs 的程序结构图了解 FatFs 在程序中的关系网络 用户应用程序需要由用户编写&#xff0c;想实现什么功能就编写什么的程序&#xff0c;一般我们只用到 f_mount()、f_open()、 f_write()、f_read() 就可以…

【vue】watchEffect 自动侦听器

watchEffect&#xff1a;自动监听值的变化 获取旧值时&#xff0c;不是很方便&#xff0c;建议用watch <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevic…

Android Studio 使用Flutter开发第一个Web页面(进行中)

附上Flutter官方文档 1、新建Flutter项目&#xff08;需要勾选web选项&#xff09; 新建项目构成为&#xff1a; 2、配置 Flutter 使用 path 策略 官方文档 在main.dart中&#xff0c;需要导入flutter_web_plugins/url_strategy.dart包&#xff0c;并在main(){}函数中usePath…

影响小程序SSL证书收费标准的因素有哪些?

在当今互联网时代&#xff0c;移动应用发展日新月异&#xff0c;小程序逐渐成为广大企业和个人开发者的心仪之选。然而&#xff0c;伴随小程序的广泛应用&#xff0c;安全问题和用户信任显得尤为关键。为了确保小程序的信息传输安全&#xff0c;SSL证书成为了一项基础配置。那么…

MySQL 表管理

目录 建库 语法&#xff1a; 库名命名规则&#xff1a; 相关命令&#xff1a; 建表 语法&#xff1a; 相关命令&#xff1a; 修改表 语法&#xff1a; 常用操作命令 复制表 数据类型 MySQL的10种常用数据类型&#xff1a; 数据的导入和导出 导入&#xff1a; 格…

Acrobat Pro DC 2023 for mac直装激活版 pdf编辑处理工具

Acrobat Pro DC 2023 for Mac是一款功能强大的PDF编辑器&#xff0c;为用户提供了全面且高效的PDF处理体验。 软件下载&#xff1a;Acrobat Pro DC 2023 for mac直装激活版下载 首先&#xff0c;它支持用户从现有文档创建PDF&#xff0c;或者将其他文件格式如图片、网页等轻松转…

SpringBoot之集成Redis

SpringBoot之集成Redis 一、Redis集成简介二、集成步骤2.1 添加依赖2.2 添加配置2.3 项目中使用 三、工具类封装四、序列化 &#xff08;正常都需要自定义序列化&#xff09;五、分布式锁&#xff08;一&#xff09;RedisTemplate 去实现场景一&#xff1a;单体应用场景二&…

自动化测试(selenium篇)

这次我们来介绍selenium 我们主要来讲解这几个要点 1.什么是自动化测试 2.什么是selenium 3.为什么来讲selenium 4.selenium的环境搭建 5.selenium的 API 1.什么是自动化测试 自动化测试指软件测试的自动化&#xff0c;在预设状态下运行应用程序或者系统&#xff0c;预设条…

海外媒体发稿:探究7个旅游业媒体套餐背后的秘密-华媒舍

旅游业媒体套餐对于旅游行业来说扮演着重要的角色&#xff0c;帮助企业在竞争激烈的市场中宣传推广&#xff0c;吸引更多的游客。在这篇文章中&#xff0c;我们将深入探究7个旅游业媒体套餐背后的秘密&#xff0c;为您揭示其真正的价值和影响。 1. 平台选择的关键 在选择旅游业…

【40分钟速成智能风控11】数据测试与应用

目录 ​编辑 数据测试与应用 联合建模机制 数据质量评估 覆盖率 稳定性 模型效果 投资回报率 线上应用 数据安全合规 数据测试与应用 智能风控模型的搭建离不开机构内外部的数据源&#xff0c;如何从海量数据源中挑选出最合适的部分进行特征工程和风控建模&#xff…

高创新 | [24年新算法]NRBO-XGBoost回归+交叉验证基于牛顿拉夫逊优化算法-XGBoost多变量回归预测

高创新 | [24年新算法]NRBO-XGBoost回归交叉验证基于牛顿拉夫逊优化算法-XGBoost多变量回归预测 目录 高创新 | [24年新算法]NRBO-XGBoost回归交叉验证基于牛顿拉夫逊优化算法-XGBoost多变量回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现 [24年新算…

Centos7 搭建Mongodb 分片集群4.0/ PSA(三成员副本集)

MongoDB 简介:1、优点和缺点:2、MongoDB适用的业务场景:Centos7 搭建Mongodb 分片集群一、安装MongoDB社区版4.01、配置程序包管理系统(`yum`)2、安装对应版本的MongoDB软件包。3、创建运行mongodb的目录并禁用SELinux4、修改文件打开数5、初始化系统5.1、创建config配置…

性能测试-数据库优化二(SQL的优化、数据库拆表、分表分区,读写分离、redis)

数据库优化 explain select 重点&#xff1a; type类型&#xff0c;rows行数&#xff0c;extra SQL的优化 在写on语句时&#xff0c;将数据量小的表放左边&#xff0c;大表写右边where后面的条件尽可能用索引字段&#xff0c;复合索引时&#xff0c;最好按复合索引顺序写wh…

请求分发场景下的鉴权问题

说明&#xff1a;记录一次对请求分发&#xff0c;无法登录系统的问题。 场景 如下&#xff0c;在此结构下&#xff0c;如何判断该用户是已登录的用户&#xff1b; 常规操作&#xff0c;用户登录后给用户发Token&#xff0c;同时将发放的Token存入到Redis中。要求用户后续请求…

【Jenkins】Jenkins自动化工具介绍

目录 技术背景常规的手动打包步骤 Jenkins简介起源与发展Jenkins的核心价值1.自动化1.1代码构建1.2测试自动化1.3自动部署 2.持续集成与持续部署CI/CD的概念如何减少集成问题更快速地发布软件版本 Jenkins优势Jenkins的主要竞争对手Travis CI:CircleCI:GitLab CI: Jenkins与其他…

Flutter第九弹 构建列表元素间距

目标&#xff1a; 1&#xff09;Flutter Widget组件之间间距怎么表示&#xff1f; 2&#xff09;列表怎么定义子项之间间距&#xff1f; 一、间距的表示组件 列表组件的间距一般采用固定间距&#xff0c;间距占据可见的空间。 已经使用的表示间距的组件 Spacer&#xff1a…

VUE_H5页面跳转第三方地图导航,兼容微信浏览器

当前项目是uniapp项目&#xff0c;若不是需要替换uni.showActionSheet选择api onMap(address , organName , longitude 0, latitude 0){var ua navigator.userAgent.toLowerCase();var isWeixin ua.indexOf(micromessenger) ! -1;if(isWeixin) {const mapUrl_tx "…

TripoSR: Fast 3D Object Reconstruction from a Single Image 论文阅读

1 Abstract TripoSR的核心是一个基于变换器的架构&#xff0c;专为单图像3D重建设计。它接受单张RGB图像作为输入&#xff0c;并输出图像中物体的3D表示。TripoSR的核心包括&#xff1a;图像编码器、图像到三平面解码器和基于三平面的神经辐射场&#xff08;NeRF&#xff09;。…