【C++】2.10智能指针详解

一、使用

1. 抛异常的安全性

int calc(int a, int b) { if (b == 0) { throw("by 0"); } else { return a / b; } } void func() { int* p1 = new int[10]; calc(1, 0); }
  • 在这个函数中,calc抛出异常,int数组就不会被析构,造成内存泄露。

  • 除非在每一个new后面都跟着一个delete[] p1;

2. RAII 和智能指针设计思路

  • 利用对象生命周期管理资源的获取和释放,将资源托管给类的析构函数。

  • 并且重载了*->等运算符进行调用,使其表现得像指针。

int calc(int a, int b) { if (b == 0) { throw("by 0"); } else { return a / b; } } void func() { SmartPtr<int> p1 = new int[10]; calc(1, 0); }
  • 异常时,智能指针对象一定会析构,就能带着其管理的int*资源一起析构。

3. C++ 标准库的智能指针

  • 拷贝问题:由于智能指针是仿指针,因此拷贝也需要仿照指针进行浅拷贝,但这会带来析构两次的问题。

  1. auto_ptr: C++98 的智能指针,极其不推荐使用,因为它会让被拷贝的源指针悬空。
    https://media/image1.png

  2. unique_ptr: 不能拷贝,除非被move

  3. shared_ptr: 用引用计数实现拷贝。

    shared_ptr<int> p1(new int[10]); shared_ptr<int> p2 = p1; cout << p1.use_count();
    • 还可以通过use_count()查看引用计数。

二、shared_ptr 实现

1. 成员变量

T* _ptr; int* _count;
  • 由于静态变量会统计所有智能指针的个数,而智能指针可能会指向不同的资源,因此不能用静态变量来计数。

  • 需要使用动态开辟的指针,当构造时就开辟新的空间来存储计数。

2. 构造函数

shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr) ,_count(sp._count) { (*_count)++; } shared_ptr(T* ptr) :_ptr(ptr) { _count = new int(1); }
  • 拷贝构造时,引用计数加一。

  • 用原始指针构造时,新开一块空间对_count进行初始化计数。

3. 析构函数

~shared_ptr() { if (--(*_count) == 0) { delete _ptr; delete _count; } }
  • 只有引用计数到达 0 时才释放管理的资源。

4. 解引用操作

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

5. 获取引用计数

int use_count() { return *_count; }

6. 赋值运算符重载(重点)

  • 需要先处理当前智能指针管理的资源(如果引用计数到 0 则析构)。

  • 然后接收新的资源,并对新的引用计数加一。

shared_ptr<T>& operator=(const shared_ptr<T>& sp) { if (sp._ptr != _ptr) { if (--(*_count) == 0) { delete _ptr; delete _count; } _count = sp._count; _ptr = sp._ptr; ++(*_count); } return *this; }

三、定制删除器

  • 由于智能指针指向的资源不一定是new出来的单个T类型对象,还可能是T数组,甚至是打开的文件句柄。

  • 直接用deletedelete[]释放可能会出错。

  1. 模板特化

    • 对于数组,可以使用shared_ptr<Date[]>,库会进行模板特化,调用delete[]

      shared_ptr<Date[]> d1(new Date[10]);
  2. 仿函数

    • 对于文件等资源,delete无法释放。

      shared_ptr<FILE> p3(fopen("源.cpp", "r")); // 错误,会造成内存泄露
    • 需要定义删除器。

      struct del { void operator()(FILE* fp) const { if (fp) { fclose(fp); std::cout << "文件已关闭" << std::endl; } } }; shared_ptr<FILE> p3(fopen("源.cpp", "r"), del());
  3. unique_ptr的删除器

    • unique_ptr的删除器类型需要在模板参数中指定,而不是在构造函数中传入。

      unique_ptr<FILE, del> p4(fopen("源.cpp", "r"));
    • 也可以使用lambda表达式,但需要用decltype推导其类型。

      auto fclosefunc = [](FILE* ptr) { fclose(ptr); }; unique_ptr<FILE, decltype(fclosefunc)> p5(fopen("源.cpp", "r"), fclosefunc);
  4. 模拟实现定制删除器

    • 由于需要在构造函数中接收仿函数,可以用function包装器来存储。

      function<void(T*)> _del = [](T* t) { delete t; };
    • 提供两个构造函数重载,区分是否传入自定义删除器。

      shared_ptr(T* ptr) :_ptr(ptr) { _count = new int(1); } template <class D> shared_ptr(T* ptr, D del) : _ptr(ptr) , _del(del) { _count = new int(1); }
    • 析构时调用包装器存储的函数。

      ~shared_ptr() { if (--(*_count) == 0) { _del(_ptr); delete _count; } }
  5. make_shared

    • 由于shared_ptr需要为对象和控制块(包含引用计数等)分别开辟空间,可能会产生内存碎片。

    • make_shared可以将这两个部分一次性连续开辟,减少碎片。

      cpp

      shared_ptr<Date> p6 = make_shared<Date>();

四、循环引用

  • 考虑在双向链表中使用shared_ptr

    struct ListNode { int _data; ListNode* _next; ListNode* _prev; ~ListNode() { cout << "~ListNode()" << endl; } }; shared_ptr<bit::ListNode> p1(new bit::ListNode); shared_ptr<bit::ListNode> p2(new bit::ListNode); p1->_next = p2; // 错误,类型不匹配
  • 在链表的节点里,如果要用智能指针管理,由于_next为普通指针,p2为智能指针,因此无法直接赋值。

  • 需要将节点内的指针也改为智能指针类型。

    struct ListNode { int _data; std::shared_ptr<ListNode> _next; std::shared_ptr<ListNode> _prev; ~ListNode() { cout << "~ListNode()" << endl; } };
  • 但是,这样做会导致内存泄露!

  • 原因分析:

    • 第一个节点的资源由p1p2_prev指向,引用计数为 2。

    • 第二个节点的资源由p2p1_next指向,引用计数也为 2。

    • 函数结束时,p1p2先析构,两个节点的引用计数都变为 1。

    • 接着,第一个节点要析构,需要等待第二个节点的_prev析构。

    • 第二个节点要析构,需要等待第一个节点的_next析构。

    • 这就陷入了循环等待,导致两个节点都无法被释放。

  • 解决方案:weak_ptr

    • 将节点内的指针改为weak_ptr

      struct ListNode { int _data; std::weak_ptr<ListNode> _next; std::weak_ptr<ListNode> _prev; ~ListNode() { cout << "~ListNode()" << endl; } };
    • weak_ptr指向资源但不会增加引用计数,从而打破了循环引用,资源可以正常析构。

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

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

相关文章

【雷达回波】高频地波雷达电离层回波方向估计Matlab实现

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#…

幼儿园老师必备:用Qwen镜像快速制作教学用动物插图

幼儿园老师必备&#xff1a;用Qwen镜像快速制作教学用动物插图 1. 引言 1.1 教学场景中的视觉需求痛点 在幼儿园日常教学中&#xff0c;生动有趣的视觉素材是吸引孩子注意力、提升学习兴趣的关键。无论是讲述动物故事、教授自然知识&#xff0c;还是设计手工活动&#xff0c…

企业级租房管理系统管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】

系统架构设计### 摘要 随着城市化进程的加速和人口流动性的增加&#xff0c;租房市场需求持续增长&#xff0c;传统租房管理模式在效率、安全性和数据管理方面面临诸多挑战。企业级租房管理系统通过信息化手段优化租房流程&#xff0c;提升管理效率&#xff0c;降低运营成本&am…

Meta-Llama-3-8B-Instruct案例展示:打造个性化AI助手

Meta-Llama-3-8B-Instruct案例展示&#xff1a;打造个性化AI助手 1. 引言&#xff1a;为什么选择Meta-Llama-3-8B-Instruct构建对话系统&#xff1f; 随着大模型技术的快速演进&#xff0c;如何在有限算力条件下部署高性能、可交互的AI助手成为开发者关注的核心问题。Meta-Ll…

论文信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】

系统架构设计### 摘要 随着信息技术的快速发展&#xff0c;高校及科研机构对论文管理的需求日益增长。传统的手工管理方式效率低下&#xff0c;容易出错&#xff0c;且难以满足大规模数据存储和检索的需求。论文信息管理系统通过数字化手段&#xff0c;实现了论文信息的集中存储…

大型商场应急预案管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】

系统架构设计### 摘要 随着城市化进程的加速和商业活动的日益繁荣&#xff0c;大型商场作为人流密集的公共场所&#xff0c;其安全管理问题备受关注。传统应急预案管理多依赖人工操作&#xff0c;效率低下且容易出现信息滞后或遗漏&#xff0c;难以应对突发事件的快速响应需求…

SAM 3功能实测:视频物体追踪效果如何?

SAM 3功能实测&#xff1a;视频物体追踪效果如何&#xff1f; 1. 引言 随着视觉基础模型的快速发展&#xff0c;Meta推出的SAM&#xff08;Segment Anything Model&#xff09;系列持续引领图像与视频分割领域的技术前沿。继SAM和SAM 2之后&#xff0c;SAM 3作为最新一代统一…

SenseVoice Small语音理解模型深度体验|支持多语言与情感识别

SenseVoice Small语音理解模型深度体验&#xff5c;支持多语言与情感识别 1. 引言&#xff1a;语音理解技术的新范式 随着大模型在语音领域的持续渗透&#xff0c;传统的自动语音识别&#xff08;ASR&#xff09;已逐步向“富转录”&#xff08;Rich Transcription&#xff0…

从贝多芬到柴可夫斯基|NotaGen一键生成古典乐

从贝多芬到柴可夫斯基&#xff5c;NotaGen一键生成古典乐 在AI音乐创作迅速发展的今天&#xff0c;传统MIDI序列建模方法正面临表达力不足、风格迁移困难等瓶颈。尤其是在古典音乐这一高度结构化且情感丰富的领域&#xff0c;如何让机器真正“理解”巴洛克的严谨、浪漫主义的激…

【毕业设计】SpringBoot+Vue+MySQL 学生网上请假系统平台源码+数据库+论文+部署文档

系统架构设计### 摘要 随着教育信息化的快速发展&#xff0c;传统纸质请假流程效率低下、审批周期长的问题日益凸显。学生请假涉及多方协作&#xff0c;包括学生提交、辅导员审批、院系审核等环节&#xff0c;传统方式容易造成信息滞后和沟通不畅。同时&#xff0c;学校管理部门…

Whisper Large v3教程:构建语音搜索API服务

Whisper Large v3教程&#xff1a;构建语音搜索API服务 1. 引言 随着多语言内容的快速增长&#xff0c;语音识别技术在跨语言信息检索、智能客服、教育辅助等场景中扮演着越来越重要的角色。OpenAI发布的Whisper系列模型&#xff0c;凭借其强大的多语言支持和高精度转录能力&…

2026年质量好的河道栏杆品牌推荐,选哪家更专业? - 品牌宣传支持者

在2026年选择专业的河道栏杆品牌时,应重点考察企业的行业经验、技术研发能力、产品质量稳定性以及项目案例的实际效果。经过对行业多家企业的综合评估,我们推荐以下五家各具特色的专业厂商,其中上海徽茸景观工程有限…

基于SpringBoot+Vue的校园社团信息管理管理系统设计与实现【Java+MySQL+MyBatis完整源码】

系统架构设计### 摘要 随着高校规模的不断扩大和学生社团活动的日益丰富&#xff0c;传统的人工管理方式已经难以满足社团信息高效管理的需求。校园社团信息管理系统能够有效解决社团活动管理混乱、信息更新不及时、资源分配不均衡等问题。该系统通过数字化手段实现社团信息的集…

Whisper语音识别模型剪枝:参数量化与加速推理

Whisper语音识别模型剪枝&#xff1a;参数量化与加速推理 1. 引言 1.1 项目背景与挑战 在构建基于 OpenAI Whisper Large v3 的多语言语音识别 Web 服务过程中&#xff0c;尽管其具备强大的跨语言转录能力&#xff08;支持99种语言&#xff09;&#xff0c;但其庞大的模型规…

VisualGGPK2终极指南:免费开源的流放之路资源编辑器完全教程

VisualGGPK2终极指南&#xff1a;免费开源的流放之路资源编辑器完全教程 【免费下载链接】VisualGGPK2 Library for Content.ggpk of PathOfExile (Rewrite of libggpk) 项目地址: https://gitcode.com/gh_mirrors/vi/VisualGGPK2 VisualGGPK2是一款专为《流放之路》游戏…

2026年靠谱的电力变电站机柜空调品牌哪家质量好? - 品牌宣传支持者

在电力变电站领域,机柜空调作为保障设备稳定运行的关键部件,其质量直接关系到变电站的安全性和可靠性。选择优质的机柜空调品牌应综合考虑技术实力、产品性能、行业应用经验及售后服务能力。经过对行业技术发展趋势、…

开源大模型2026年展望:Qwen3-4B+弹性GPU部署实践

开源大模型2026年展望&#xff1a;Qwen3-4B弹性GPU部署实践 1. 技术背景与趋势 随着大模型在自然语言处理、代码生成和多模态任务中的广泛应用&#xff0c;轻量级高性能开源模型正成为企业与开发者落地AI应用的关键选择。2025年以来&#xff0c;以Qwen系列为代表的中等规模模…

永辉超市卡回收哪家好,认准合规平台 - 京回收小程序

永辉超市卡回收哪家好,认准合规平台闲置的永辉超市卡若长期搁置,不仅会浪费资源,还可能因过期造成损失。永辉超市卡回收的关键的是选择正规平台,既能保障资金安全,又能高效盘活闲置资产。其中猎卡回收凭借完善的资…

Qwen3-4B-vLLM集成优势?高吞吐部署性能提升50%教程

Qwen3-4B-vLLM集成优势&#xff1f;高吞吐部署性能提升50%教程 1. 引言&#xff1a;为何选择 Qwen3-4B-Instruct-2507 vLLM&#xff1f; 随着大模型从云端向端侧下沉&#xff0c;轻量级、高性能的小模型成为边缘计算、本地推理和实时应用的关键载体。通义千问 3-4B-Instruct…

AI文档处理案例:电商行业订单处理自动化

AI文档处理案例&#xff1a;电商行业订单处理自动化 1. 业务场景与痛点分析 在电商行业的日常运营中&#xff0c;订单处理是核心环节之一。无论是来自线下渠道的手写订单、供应商发票&#xff0c;还是客户提交的退货凭证&#xff0c;这些信息往往以纸质文档的形式存在。传统的…