当前位置: 首页 > news >正文

【c++】智能指针

智能指针

  • 智能指针
    • RAII和智能指针的设计思路
    • 什么是内存泄漏
    • 内存泄漏的危害
    • 内存泄漏场景
    • C++标准库智能指针的使⽤
      • auto_ptr
      • unique_ptr
      • shared_ptr
        • shared_ptr循环引用问题
      • weak_ptr和shared_ptr
      • shared_ptr线程安全问题

智能指针

在c++中,我们需要注意对内存资源的管理,若在程序中发生内存泄漏,这是一个非常可怕的事情。刚开始可能无事发生,但是随着内存资源的泄漏,最后就会导致程序崩溃。
但是内存资源也是一件麻烦事,所以c++提供了智能指针,将申请到的内存对象交给智能指针对象来管理。

RAII和智能指针的设计思路

RAII是ResourceAcquisition Is Initialization,即资源申请立即初始化。利用对象的生命周期来管理内存资源、网络连接、互斥锁等。RAII在获取到资源后,立即将资源交付给对象,在对象生命周期内,资源始终有效,当对象析构时,资源释放,这样就避免了资源泄漏问题

#include<iostream>#include<memory>{shared_ptr<string>sp1=make_shared<string>("111");unique_ptr<string>sp1=make_shared<string>("111");return0;}

什么是内存泄漏

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释 放或者发⽣异常释放程序未能执⾏导致的。
内存泄漏并不是指内存在物理上的消失,⽽是应⽤程序分 配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。

内存泄漏的危害

内存泄漏的危害:普通程序运⾏⼀会就结束了出现内存泄漏问题也不⼤,进程正常结束,⻚表的映射 关系解除,物理内存也可以释放。⻓期运⾏的程序出现内存泄漏,影响很⼤,如操作系统、后台服 务、⻓时间运⾏的客⼾端等等,不断出现内存泄漏会导致可⽤内存不断变少,各种功能响应越来越 慢,最终卡死。

内存泄漏场景

下面以抛异常举例

voidDavid(intx,intb){if(b==0)throw("b不能为0");}voidfunc(){int*arr1=newint[10];int*arr2=newint[10];try{//若这里抛出除0异常,且当前作用域的catch不匹配//程序会跳到main函数中,下面catch中的delete不会被执行//资源不会被释放,造成资源泄漏intlen=0,time=0;cin>>len>>time;David(len,time);}catch(constchar*errmsg){//每次都要对异常进行捕捉,然后释放资源,再抛出异常delete[]arr1;delete[]arr2;throw;}}intmain(){try{func();}catch(constchar*errmsg){cout<<errmsg<<endl;}catch(constexception&errmsg){cout<<errmsg.what()<<endl;}catch(...){cout<<"未知错误"<<endl;}return0;}

C++标准库智能指针的使⽤

C++标准库中的智能指针都在<memory>头⽂件下⾯,我们包含就可以是使⽤了, 智能指针有好⼏种,除了weak_ptr他们都符合RAII和像指针⼀样访问的⾏为,原理上而言主要是解决智能指针拷贝时的思路不同。

auto_ptr

auto_ptr是在c++98提出的,这是一个非常糟糕的设计,因为在拷贝时,会发生资源管理权转移的问题,造成智能指针对象悬空,访问报错的问题。

auto_ptr<int>ap1(newint(1));

ap1在拷贝构造ap2时,资源的管理权被转移到了ap2,ap1悬空。

所以对于auto_ptr,尽量不要用

unique_ptr

是C++11设计出来的智能指针,唯一指针,不支持拷贝,只支持移动。如果不需要拷⻉的场景就⾮常建议使⽤他。
它也会发生资源管理权的转移,但与auto_ptr不同的是,unique_ptr是否对资源管理权的转移,是由程序员决定的,而auto_ptr发生资源管理权转移时,程序员自己可能都不知道。

intmain(){unique_ptr<Date>up1(newDate);unique_ptr<Date>up2(move(up1));return0;}


unique_ptr只支持移动构造,不支持拷贝构造,所以当发生资源管理权转移时,必定是程序员对unique_ptr对象move了。资源管理权转移变的可控了

shared_ptr

是C++11设计出来的智能指针,共享指针,支持拷贝构造也支持移动构造,需要进行拷贝的场景就要使用它。底层是⽤引⽤计数的⽅式实现的。

  • 它允许多个shared_ptr指针指向同一块资源,底层用一个“计数器”来记录。

classDate{public:Date(intyear=1,intmonth=1,intday=1):_year(year),_month(day),_day(day){}private:int_year;int_month;int_day;};intmain(){shared_ptr<Date>sp1(newDate);shared_ptr<Date>sp2(sp1);return0;}

use_count()接口返回有多少shared_ptr在管理该资源

shared_ptr支持new type[]

shared_ptr<Date[]>sp1(newDate[]);

说到这个,就要提到另一个东西“定制删除器

template<classU,classD>shared_ptr(U*p,D del);

删除器可以是:

  • lambda
  • 仿函数
  • 函数指针
//传lambdashared_ptr<Date>sp2(newDate[10],[](Date*ptr){delete[]ptr;});//传入放函数shared_ptr<FILE>sp3(fopen("...","r"),Fclose());//传函数指针shared_ptr<Date>sp3(newDate[10],Del<Date>);
shared_ptr循环引用问题

我们知道,shared_ptr在底层,会维护一个计数器,来记录有多个shared_ptr在管理该资源,这个设计避免了因为一个对象析构,释放了资源导致其他管理该资源的对象悬空。但同时页引出了一个新问题“循环引用”

structListNode{shared_ptr<ListNode>*_next;shared_ptr<ListNode>*_prev;~ListNode(){cout<<"~ListNode()"<<endl;}};intmain(){shared_ptr<ListNode>sp1(newListNode);shared_ptr<ListNode>sp2(newListNode);sp1->_next=sp2;sp2->_prev=sp1;return0;}
  • n1与n2是双向链表中的节点。sp1管理着n1节点,sp2管理着n2节点,此时让n1的_next指针指向sp2管理的资源,也就是n2节点,再让n2的_prev指针指向sp1管理的资源,也就是n1。
  • 运行程序,当程序结束时,n1与n2因该被析构,终端会打印 “~ListNode()” ,但是结果是并没有打印。


这是因为n1与n2节点的_next、_prev是智能指针,这两个节点互相指向时,shared_ptr底层对应的计数器++,即使sp1与sp2析构了,计数器–,但由于_next、_prev的原因,资源对应的计数器始终不会减为0,导致资源无法正常释放,内存泄漏。这是为什么?

如果n1节点想要释放,就需要n2节点_prev对象析构,而_prev对象析构就需要n2节点释放,而n2节点释放,就需要n1节点的_next对象析构,而_next对象析构,就需要n1节点释放,此时,形成循环,导致n1与n2节点都无法释放

为了解决循环引用问题,c++提供了weak_ptr

weak_ptr和shared_ptr

weak_ptr其实并不是智能指针。它本质就是为了解决循环引用的问题而提出的。

  • 当weak_ptr指向share_ptr管理的资源时,并不会增加引用计数。
  • 但是,引用计数还被一个count维护,当weak_ptr指向shared_ptr管理的资源时,count++。
    像上面提到的循环引用问题,为了解决该问题,我们需要将ListNode的代码该动一下
structListNode{//shared_ptr< ListNode> _prev;//shared_ptr< ListNode> _next;weak_ptr<ListNode>_prev;weak_ptr<ListNode>_next;~ListNode(){cout<<"~ListNode()"<<endl;}};

sp1管理n1节点,sp2管理n2节点,即使n1与n2节点互相指向,但是引用计数也不会++,因为ListNode的两个指针是weak_ptr,不会增加引用计数,只会增加count。
当sp1与sp2析构时,引用计数–,变为0,weak_ptr检测到计数器变为0,说明该资源过期了,就不会指向该资源了,count–,若此时count减为0,说明没有weak_ptr指向该资源了,就将资源释放了,反之就不释放。

shared_ptr线程安全问题

shared_ptr本身是线程安全的,但是指向的对象不是线程安全的,当多线程场景时,需要对临界资源进行加锁

structAA{int_a1=0;int_a2=0;~AA(){cout<<"~AA()"<<endl;}};intmain(){shared_ptr<AA>p1(newAA);mutex _mutex;autofunc=[&](){for(inti=0;i<10000;i++){shared_ptr<AA>copy(p1);{unique_lock<mutex>lx(_mutex);copy->_a1++;copy->_a2++;}}};threadt1(func);threadt2(func);t1.join();t2.join();cout<<p1->_a1<<endl;cout<<p1->_a2<<endl;return0;}
http://www.jsqmd.com/news/850420/

相关文章:

  • 2026威海市黄金白银铂金回收 全区域正规变现 诚信门店TOP5实力排行榜地址及联系方式 - 前途无量YY
  • 2026全城区上门武威市黄金铂金白银,诚信店铺靠谱TPO1 实力排行榜即地址联系方式 - 亦辰小黄鸭
  • 无机纳米抗裂防渗剂安全供应商靠谱吗 - myqiye
  • 2026昭通市黄金白银铂金回收 全区域正规变现 诚信门店TOP5实力排行榜地址及联系方式 - 前途无量YY
  • ubuntu系统下安装tftp服务器(实现uboot或者kernel单独更新增加了端口号冲突导致异常)
  • 柔和舒适,清凉不减:2026年值得推荐的无风感新风空调——海尔Seeker - 资讯焦点
  • Vue3-Mindmap深度解析:基于Vue 3的高性能思维导图组件架构设计与实现原理
  • ThinkPad双风扇控制终极指南:TPFanCtrl2完全配置教程
  • 如何选择靠谱的红色教育馆策划设计公司,盛世笔特值得选吗 - myqiye
  • 2026全城区上门西安市黄金铂金白银,诚信店铺靠谱TPO1 实力排行榜即地址联系方式 - 亦辰小黄鸭
  • 2026全城区上门丽水市黄金铂金白银,诚信店铺靠谱TPO1 实力排行榜即地址联系方式 - 亦辰小黄鸭
  • 别再只盯着Monaco了!2023年构建Web代码编辑器,CodeMirror 6可能是更好的选择
  • 2026肇庆市黄金白银铂金回收 全区域正规变现 诚信门店TOP5实力排行榜地址及联系方式 - 前途无量YY
  • 2026全城区上门西宁市黄金铂金白银,诚信店铺靠谱TPO1 实力排行榜即地址联系方式 - 亦辰小黄鸭
  • AND 婚礼多少钱?各预算区间服务详情 - myqiye
  • 2026全城区上门连云港市黄金铂金白银,诚信店铺靠谱TPO1 实力排行榜即地址联系方式 - 亦辰小黄鸭
  • 亲测有效的山西广电证申请经验分享
  • 2026潍坊市黄金白银铂金回收 全区域正规变现 诚信门店TOP5实力排行榜地址及联系方式 - 前途无量YY
  • 2026全城区上门辽源市黄金铂金白银,诚信店铺靠谱TPO1 实力排行榜即地址联系方式 - 亦辰小黄鸭
  • 如何从视频中智能提取PPT内容:3步完成自动化内容转换
  • 2026年成都散酒铺品牌怎么找?TOP7权威排行榜给你答案 - 品牌推荐官方
  • 服务号版本:weixin-java-mp=4.8.3.B,spring-boot=3.3.1,httpclient5=5.5.2
  • 机器人语义安全成本函数设计与动态规划实现
  • 终极键盘连击修复方案:KeyboardChatterBlocker 全面使用指南
  • 2026渭南市黄金白银铂金回收 全区域正规变现 诚信门店TOP5实力排行榜地址及联系方式 - 前途无量YY
  • 力扣算法面试150题——个人笔记——复习用
  • 告别盲测!用CANoe回放功能搭建你的车载网络自动化测试环境
  • ViGEmBus虚拟游戏控制器驱动:从零开始掌握Windows手柄模拟技术
  • Ubuntu环境搭建:secure CTR可以root用户登入
  • ARM与C51二进制取反操作差异解析