目录

  • 1. RAII与智能指针
  • 2. C++库中的智能指针
    • 2.1 智能指针auto_ptr
    • 2.2 智能指针unique_ptr
    • 2.3 智能指针shared_ptr
  • 3. shared_ptr的循环引用
  • 4. 智能指针的定值删除器

1. RAII与智能指针

  上一篇文章学习了异常相关的知识,其中遗留了一个异常安全相关的问题。那就是异常的抛出会打乱执行流,可能使得动态开辟的资源无法被正常释放导致内存泄漏。而之前我们是通过异常再抛出的方式去解决这一问题的,可是,此种方式会使得代码的可读性极差。下面就来学习一种更好的也是现今一般会使用的解决异常安全的方式,智能指针。
  在正式学习智能指针之前,先来了解一个概念RAII(Resource Acquisition Is Initializatio)RAII是C++中的一种编程设计思路,直译而来是资源获得后立即初始化。而实际上是指将资源交给一个对象去帮忙管理,利用对象的生命周期来管理资源,智能指针就是RAII思想设计而得一个产物。另外的应用场景,还有,打开文件与关闭文件,打开文件的返回值一般都是指针。


智能指针的特性与功能:

  • 1. 智能指针会将资源管理起来,利用本身对象的生命周期在析构时释放资源,防止了资源的内存泄漏
  • 2. 智能指针支持像指针一样的操作,诸如,解引用*箭头->
  • 3. 智能指针的拷贝不会进行深拷贝与迭代器类似,虽然其能对资源进行管理与操作,但与数据结构的存储不一样,数据结构中所存储的资源是自己的,而智能指针的资源是代为持有,多个智能指针是共享一份资源的(一般为引用计数)
//智能指针管理资源的逻辑与支持指针操作
template<class  T>
class SmartPtr
{
public://管理资源SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;}//像指针一样的操作:*, ->T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};

2. C++库中的智能指针

2.1 智能指针auto_ptr

  • 历史上的一个智能指针auto_ptr:

  在C++98标准时,就已经有了C++中历史上的第一个智能指针auto_ptr。其除了具备智能指针管理资源与指针操作的功能外,对智能指针的拷贝也做了相关的实现,其设计思路为拷贝后,将指针转移,将原有指针悬空。此种方法多有漏洞,大部分公司都禁止其的使用,可以说是C++语言历史上的一个语法污点。可能是受此影响,C++98标准后,一些C++标准委员会库工作组成员合理制作了一个名为boost的准标准库,其会将一个些新的语法点进行先探索与尝试实现,C++标准库后续的很多语法都是从boost库中吸收而来。

  • auto_ptr的拷贝后指针悬空与简单实现模拟:

在这里插入图片描述

//模拟实现
template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}//指针悬空auto_ptr(const auto_ptr<T>& p){_ptr = p._ptr;p._ptr = nullptr;}~auto_ptr(){delete _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

2.2 智能指针unique_ptr

  因为C++98中auto_ptr的缺陷,boost库中又尝试创建实现了新的智能指针,如shared_ptr(配合new)/shared_array(配合new[])共享指针、scoped_ptr/scoped_arrary守卫指针、weak_ptr弱指针、instrusive_ptr。其中,shared_ptrscoped_ptrweak_ptr都后续被纳入标准库。后续出现的这些指针都是旨在采用不同的方式去处理智能指针拷贝的问题。boost库中的scoped_ptr就是现在C++标准库中的unique_ptrunique_ptr解决拷贝问题的方式是,直接禁止本身进行拷贝操作,其原理为禁止拷贝构造与赋值重载的生成,C++11前的实现方法与C++11后的实现方法不同。
  所有智能指针都包含在<memory>头文件中,boost库中将boost作为命名空间。
在这里插入图片描述

//unique_ptr简单模拟实现
template<class T>
class unique_ptr
{
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){delete _ptr;}unique_ptr(const unique_ptr<T>& p) = delete;//拷贝构造unique_ptr<T>& operator=(const unique_ptr<T>& p) = delete;//赋值T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

2.3 智能指针shared_ptr

  与unique_ptr不同shared_ptr支持拷贝操作,其底层是以引用计数的方式来支持拷贝构造与赋值操作的实现的。但选取怎样一个变量当作为引用计数的载体是一个值得思考的问题,智能指针的引用计数是指当前有多少个智能指针指向同一份资源。

  • 选取普通的成员变量显然是不可行的,其无法保证拷贝时引用计数的共享性。
    在这里插入图片描述
  • 那么,属于整个类的静态成员变量呢?初步考量这好像是一个可行的方案,但这个方法其实还是有漏洞,当同一类型的shared_ptr指向不同的资源时,静态成员变量就无法解决了。
    在这里插入图片描述
  • C++标准库中给出的方法是,动态开辟new出一个变量,让其存储引用计数,这样指向不同资源的shared_ptr就不会互相印象,当引用计数归零时,对资源进行释放。
    在这里插入图片描述
    在这里插入图片描述

shared_ptr的简单模拟实现:

template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr = nullptr)//构造参数赋予缺省值,充当默认构造:_ptr(ptr){_pcount = new int(1);}void release(){if (--(*_pcount) == 0)//引用计数为0时,释放资源{delete _ptr;delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& p){_ptr = p._ptr;_pcount = p._pcount;(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& p){//不能自己给自己赋值//if(*this != p)//指向同一份资源的智能指针赋值,特殊处理if (_ptr != p._ptr){release();_ptr = p._ptr;_pcount = p._pcount;(*_pcount)++;}return *this;}int use_count(){return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const//指针指向的对象不能被改变{return _ptr;}private:T* _ptr;int* _pcount;
};

3. shared_ptr的循环引用

  shared_ptr在多种智能指针中,综合而论已经是最优秀的智能指针了,可时它真的就完全不会造成内存泄漏的问题了吗,我们来看下面这个场景。
在这里插入图片描述
  自定义一个双向链表的节点,链表的每个节点都是动态开辟而出的,这里我们采用智能指针的方式去定义与管理。但当程序执行结束时,两个链表节点的资源并没有被释放。
在这里插入图片描述
  当程序运行结束,node1、node2两个shared_ptr智能指针销毁之后。还有两个指向节点资源的智能指针_next_prev。这就使得指向节点资源的智能指针其引用计数没有归0,所指向的资源也就无法释放。被智能指针管理的资源想要被释放,其引用计数就需要归0,想要引用计数归0,那么,所有指向该资源的智能指针都必须要销毁。但在这一过程中,会出现下图的逻辑闭环,导致节点1,节点2互相指向无法释放的逻辑闭环,造成循环引用,内存泄漏
在这里插入图片描述


循环引用的解决方法weak_ptr:
  为了解决上述shared_ptr的循环引用导致内存泄漏的问题,C++库中设计了weak_ptr这样一个智能指针,其的种种普通特性都与shared_ptr智能指针相同,但特殊的是,使用它指向shared_ptr管理的资源,shared_ptr的引用计数不增加。weak_ptr只做链接功能,因为weak_ptr与shared_ptr不是一个类型的智能指针,weak_ptr想要从shared_ptr获取资源只有两个方式,一是被声明为友元,二是为shared_ptr添加get接口,get接口必须使用const修饰this指针。

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

在这里插入图片描述
  在上面weak_ptr智能指针的模拟实现中,没有为其添加引用计数。但在C++标准库中weak_ptr其实也是有引用计数的。只不过它的引用计数不参与空间的释放,weak_ptr的引用计数更像是一种监视,其的存在是为了防止weak_ptr去释放引用计数为0已经被释放过的空间。

4. 智能指针的定值删除器

  上面所有关于智能指针的学习,对于shared_ptr智能指针地模拟实现,都是基于使用智能指针对单个动态开辟new出的对象做管理的情况。但当需要使用智能指针管理多个对象,或是管理非动态开辟的资源(文件指针)时,就无法去正确地释放资源了
在这里插入图片描述
  boost库中对于管理多个对象的资源创造了专门与之相对应的shared_array与scoped_array。但在C++标准库中,却没有采用这种方式,而是设计了一种定值删除器的方法来控制对资源的删除方式,使用方式如下:

//方法1:C++中特化模板,专门用于释放new[]的资源
shared_ptr<ListNode[]> p1(new ListNode[10]);//方法2:定值删除器,构造时传入以仿函数对象形式传入对应的资源释放方法
template<class T>
struct DeleteArray
{void operator()(T* ptr){delete[] ptr;}
};//仿函数、lambda表达式、函数指针皆可
shared_ptr<ListNode> p2(new ListNode[10], DeleteArray<ListNode>());

C++标准库中的调用接口:
在这里插入图片描述


  • shared_ptr中定值删除器的模拟实现
namespace zyc
{template<class T>class shared_ptr{public://定值删除器function<void(T*)> _del = [](T* ptr) { delete ptr; };//delete普通new对象的缺省处理方法template<class D>shared_ptr(T* ptr, D del):_del(del){}shared_ptr(T* ptr = nullptr):_ptr(ptr){_pcount = new int(1);}void release(){if (--(*_pcount) == 0){_del(_ptr);//控制释放方式delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& p){_ptr = p._ptr;_pcount = p._pcount;(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& p){if (_ptr != p._ptr){release();_ptr = p._ptr;_pcount = p._pcount;(*_pcount)++;}return *this;}private:T* _ptr;int* _pcount;};
}

  含有定值删除器的模板构造中,其中定值包装器的类型是独属于此构造函数的模板参数类型,而不是整个类。因此,想要通过成员变量的方式让析构函数拿到这一仿函数对象,就需要采用定义包装器对象的方式来实现(释放资源的仿函数其参数与返回值类型是确定的)。

  • 内存泄漏与资源泄漏:
      内存泄漏是指动态开辟(malloc/realloc/new)出的空间已经不再使用,可是因为疏忽(忘记free/delete)/错误(循环引用)的原因没有去释放。而资源泄漏是指申请的资源(文件描述符,管道等)在使用完成忘记释放,资源描述符是有限的。在程序长期运行的环境下,内存泄漏可能会导致程序直接崩溃,而资源泄漏可能就会导致出现无法再打开文件等问题。
      一般出现上述问题,在不同环境下都有内存泄漏的检测工具可以帮助我们发现问题,但再好的检测工具都不如我们在编写代码时多加注意,提高代码的规范性。每到必要时就去使用智能指针管理相关资源,如此就能预防避免几乎所有的资源泄漏问题。再好的事后检测手段,都不如事前做好预防
      使用cout打印char类型指针变量时,需要进行(void)强制类型转换,因为cout会默认char*类型为打印字符串。

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

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

相关文章

Tkinter 实现按钮鼠标悬浮提示:两种方案(继承Frame与不继承)

在 Tkinter 桌面应用开发中&#xff0c;为按钮添加“鼠标悬浮提示”是提升用户体验的常用功能——无需点击&#xff0c;只需将鼠标挪到按钮上方&#xff0c;就能自动显示按钮功能说明。本文将详细介绍两种实现方案&#xff1a;不继承 Frame 类&#xff08;快速简洁版&#xff0…

20250814 最小生成树总结

引子 啊啊额&#xff0c;从一张图里抽出几条边&#xff0c;组成一棵树&#xff0c;无环n−1n-1n−1条边&#xff0c;就是生成树。那么边权和最小的生成树就叫最小生成树&#xff0c;最大生成树同理。 kruskal最小生成树 要求kruskal最小生成树&#xff0c;我们首先用结构体数组…

数据大集网:实体店获客引流的数字化引擎,解锁精准拓客新密码​

​在实体店面临流量焦虑、获客成本攀升的当下&#xff0c;实体店获客引流工具的重要性愈发凸显。如何在激烈的市场竞争中精准触达目标客户、构建可持续的客流增长模式&#xff1f;数据大集网凭借其创新的智能获客体系与全链路服务能力&#xff0c;正成为万千实体店突破增长瓶颈…

nginx --ssl证书生成mkcert

github https://github.com/FiloSottile/mkcert/releases网盘下载地址 https://pan.baidu.com/s/1XI0879pqu7HXZMnmQ9ztaw 提取码: 1111windows使用示例

守拙以致远:个人IP的长青之道|创客匠人

2025年被认为是AI应用全面爆发的一年。各种人工智能工具在写作、制图、剪辑等领域广泛使用&#xff0c;大大提升了个人和团队的工作效率。对于个人IP而言&#xff0c;这类工具的出现确实带来了新的机会&#xff0c;但也伴随着一种现象——一些人开始过度依赖甚至神化AI&#xf…

USB 3.0 LTSSM 状态机

USB2.0在电源供应后&#xff0c;通过Pull Up D-来决定枚举LS&#xff0c;Pull Up D有一个USB高速握手过程&#xff0c;来决定HS FS。USB3.0则会通过链路训练&#xff08;Link Training&#xff09;&#xff0c;来准备USB3.0通信。每当我们插上USB线的时候&#xff0c;对于3.0的…

MySQL窗口函数与PyMySQL以及SQL注入

MySQL窗口函数与PyMySQL实战指南&#xff1a;从基础到安全编程 引言 在数据处理和分析领域&#xff0c;MySQL作为最流行的关系型数据库之一&#xff0c;其窗口函数功能为数据分析提供了强大的支持。同时&#xff0c;Python作为数据分析的主要语言&#xff0c;通过PyMySQL库与My…

高级项目——基于FPGA的串行FIR滤波器

给大家安利一个 AI 学习神站&#xff01;在这个 AI 卷成红海的时代&#xff0c;甭管你是硬核开发者还是代码小白&#xff0c;啃透 AI 技能树都是刚需。这站牛逼之处在于&#xff1a;全程用 "变量名式" 幽默 生活化类比拆解 AI&#xff0c;从入门到入土&#xff08;啊…

JPrint免费的Web静默打印控件:PDF打印中文乱码异常解决方案

文章目录JPrint是什么&#xff1f;中文乱码&#xff08;Using fallback font xxx for xxxx&#xff09;1.字体嵌入2.客户机字体安装开源地址相关目录导航使用文档端口号修改代理使用场景打印服务切换中文乱码解决方案 JPrint是什么&#xff1f; JPrint是一个免费开源的可视化静…

MFT 在零售行业的实践案例与场景:加速文件集成与业务协作的高效方案

零售行业竞争激烈、数字化转型迭代迅速&#xff0c;业务对数据与档案的传输、处理和整合要求极高。无论是新品上市市场数据&#xff0c;还是供应链物流单据&#xff0c;集成方式不论是通过API或是档案传输, 对于传输的稳定性,安全性与性能, 都会直接影响决策效率与顾客体验。MF…

OSG+Qt —— 笔记1 - Qt窗口加载模型(附源码)

🔔 OSG/OsgEarth 相关技术、疑难杂症文章合集(掌握后可自封大侠 ⓿_⓿)(记得收藏,持续更新中…) OSG+Qt所用版本皆为: Vs2017+Qt5.12.4+Osg3.6.5+OsgQt(master) 效果 代码(需将cow.osg、reflect.rgb拷贝至工程目录下) OsgForQt.ui main.cpp

开源安全云盘存储:Hoodik 实现端到端数据加密,Docker快速搭建

以下是对 Hoodik 的简单介绍&#xff1a; Hoodik 是一个使用 Rust 和 Vue 开发的轻量级自托管安全云存储解决方案采用了非对称RSA密钥对和AES混合加密策略&#xff0c;从文件存储加密到数据链路加密&#xff0c;全程保证数据安全支持Docker一键私有部署&#xff0c;数据和服务…

[C++] Git 使用教程(从入门到常用操作)

1. Git 简介 Git 是一款分布式版本控制系统&#xff0c;用来跟踪文件变化、协作开发、管理项目版本。 它是开源的&#xff0c;由 Linus Torvalds 在 2005 年开发&#xff0c;广泛用于开源与企业项目中。 2. 安装 Git Windows 前往 Git 官网 下载并安装。 安装时建议勾选 Git…

实盘回测一体的期货策略开发:tqsdk获取历史数据并回测,附python代码

原创内容第969篇&#xff0c;专注AGI&#xff0c;AI量化投资、个人成长与财富自由。 星球好多同学希望说说实盘&#xff0c;我们就从实盘开始吧。 我们选择tqsdk给大家讲解&#xff0c;tqsdk支持免费注册&#xff0c;使用模拟账户&#xff0c;历史和实时数据&#xff0c;方便…

大模型推理框架vLLM 中的Prompt缓存实现原理

背景&#xff1a;为什么需要Prompt缓存模块&#xff1f;在大模型问答多轮对话应用场景中&#xff0c;不同请求的 Prompt 往往有相同的前缀&#xff0c;比如&#xff1a;第一次问答&#xff1a;你是一名专业的电子产品客服&#xff0c;负责回答客户关于手机产品的咨询。请根据以…

Python之Django使用技巧(附视频教程)

概述 Django 是一个高级的 Python Web 框架&#xff0c;遵循 “batteries-included”&#xff08;内置电池&#xff09;理念&#xff0c;提供了构建 Web 应用所需的大部分组件&#xff0c;让开发者可以专注于业务逻辑而不是底层细节。视频教程&#xff1a;https://pan.quark.cn…

sqli-labs通关笔记-第44关 POST字符型堆叠注入(单引号闭合 手工注入+脚本注入3种方法)

目录 一、堆叠注入 二、源码分析 1、代码审计 2、SQL注入安全性分析 三、堆叠手注法 1、进入靶场 2、正确用户名密码登录 3、堆叠注入 4、查看数据库 四、联合手注法 1、获取列数 2、确认回显位 3、获取数据库名 4、获取表名 5、获取列名 6、获取字段 7、总结…

从深度伪造到深度信任:AI安全的三场攻防战

前言当大模型开始“睁眼”看世界&#xff0c;伪造者也开始“闭眼”造世界。2025 WAIC释放出的信号很明确&#xff1a;没有AI安全底座&#xff0c;就没有产业智能化的高楼。WAIC 把“安全”摆在与“创新”同等重要的位置&#xff0c;形成了“1 份共识框架&#xff0b;2 份重磅报…

【C++】哈希的应用:位图和布隆过滤器

目录 一、位图 1.1 位图的概念 1.2 位图的实现 1.3 位图的应用 二、布隆过滤器 2.1 布隆过滤器的提出 2.2 布隆过滤器的概念 2.3 布隆过滤器的插入和查找 2.4 布隆过滤器的删除 2.5 布隆过滤器的优点 2.6 布隆过滤器的缺点 一、位图 1.1 位图的概念 1. 面试题 给4…

C语言:指针(4)

1. 回调函数回调函数就是指通过函数指针调用的函数。如果将函数指针作为参数传递给另一个函数&#xff0c;另一个函数根据指针来调这个函数&#xff0c;那么被调用的函数就是回调函数。回调函数不是由这个函数的实现方直接调用&#xff0c;而是在特定的条件下由另一方调用的。例…