zoukankan      html  css  js  c++  java
  • C++的智能指针(shared_ptr,weak_ptr)

    一:概述

    参考资料:《C++ Primer中文版 第五版》
    我们知道除了静态内存和栈内存外,每个程序还有一个内存池,这部分内存被称为自由空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们。

    在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

    动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

    为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的两种智能指针的区别在于管理底层指针的方法不同,

    shared_ptr允许多个指针指向同一个对象。属于强引用类型,他会维护引用计数的信息,即会记录当前有多少个存活的 shared_ptrs 正持有该对象. 共享的对象会在最后一个强引用离开的时候销毁( 也可能释放).

    unique_ptr则“独占”所指向的对象。

    标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,他不控制对象生命周期, 虽然指向shared_ptr指针指向的对象的内存,却并不拥有该内存。

    这三种智能指针都定义在memory头文件中。

    二:解析

    为什么把上面这段话全部粘贴下来,因为真的说的太好了,都是干货,不仅简单阐述了智能指针的应用场景和原理,还帮我们回忆了一些基本知识,所以提取出来的知识点有

    1,局部变量都是桟里的,所以没事。所以并不是说所有的指针都要担心他的释放而使用智能指针,而是当指针指向是一块堆中分配的内存的时候

    2,new和delete是一对,delete释放的内存是级联的么?这个疑问先保留。

    3, 首先分配的堆内存就那么一块,一旦这块内存被shared_ptr类型的指针指向,那么他在拥有这块存储的操作权的同时,还同时还知晓此时还有多少个像他一样的指针也拥有这块内存

         更要命的是,这个指针并不是单纯的指向,他还涵盖一个天大的动作,即最后一个这样类型的指针发现就剩他自己指向该对象的时候,他会在自己消亡之前把这块内存也释放了,

         即会调用对应的delete函数。

         注意:什么叫自己消亡,如果这个指针只是一个局域变量,或者自己所属的类被释放了....

    4.weak_ptr就是一个旁观者,可以看到这块内存被多少个shared_ptr引用了,而不能通过本指针去操作这块内存

    三:实践

    通过以上的逻辑,多说无意,一起实践以下,先简单介绍一下我的整体框架:

    核心类为Ele,其内部有个变量是个指针,指针指向的也是一块内存,

    这块内存是一个类的实例,并且还存在多态,即根据不同的情况实际创建的是不同的类实例,于是:

    1.核心工作类

    typedef shared_ptr<UDPProtocol> SProtocol;      //SProtocol是一个指向UDPProtocol类型内存的指针,并且是一个强引用,即他拥有访问该存储的权限,并且他的指向会增加计数引用,并并且他有权释放该内存
    typedef weak_ptr<UDPProtocol> WProtocol;       //WProtocol是指向该内存的弱引用,他可以看作是就是一个快照!!!
    
    
    struct Ele{
      ...
    
    SProtocol NewUDPProtocol(int pType);
    SProtocol GetUDPProtocol();private:
    void FreeEle();
    
    private:
    WProtocol pUdpProtocol;             //核心类只记录申请内存的快照而以,这样他的存在就不影响对象的释放,如果是强引用,那么他只要存在,申请的内存就永远无法释放。
    };
    
    
    
     vector<SProtocol> sProtocols;      //但是在对象的生命周期中,总得有个强指针一直引用着吧,因为在核心类中的WProtocol类型的变量只是个快照,他的存在是依赖强引用sProtocols。

    2.共享指针的用法:创建获取与检查,与其他

    1)创建共享指针(强引用)方式如下:

    SProtocol sh = make_shared<SIPProtocol>();

    (2)强引用指针shared_ptr有如下可用的工具函数:

    .get():可以用该函数获取底层对象的普通指针,或者说是该指针包含的裸指针。注意该裸指针的值就是底层对象的指针,也就是等同于最开始new的那块内存的地址,因此他的获取可以用来判断,具体用法见下面

    shared_ptr还定义了自己的类型转换操作符:static_pointer_cast, dynamic_pointer_cast, const_pointer_cast ,具体用法见下面代码。

    (3)弱引用指针weak_ptr不能直接访问对象,但是他有一些工具函数可以用来用

    .expired()用来查看自己监视的对象是否还在,或者他依赖的那些强引用指针们还有没有了
    .use_count()用来看看我依赖的强引用指针们现在有多个,即目前有多少个指针指向底层对象

        .lock()会检查weak_ptr指向的对象是否存在。如果存在,返回一个指向共享对象的shared_ptr,如果不存在,lock将返回一个空指针。

    代码:

    //0.核心类调用,即智能指针使用的开始
    Ele ingress;
    ingress.NewUDPProtocol(APP_PROTOCOL_SIP);
    
    //1. 创建底层实例函数,以及其他
    SProtocol Ele::NewUDPProtocol(int pType){
        SProtocol sh;
        if (pType==APP_PROTOCOL_SIP || port == 5060 ||portSer == 5060 || port == 5061 ||portSer == 5061){
            sh =make_shared<SIPProtocol>();    //根据不同的情况,创建不同的底层实例
        }else{
            sh =make_shared<UDPProtocol>();
        }
        pUdpProtocol=sh;
        appType=pType;
        sProtocols.push_back(sh);    //将强引用指针添加到vector中,维持这对象的生命周期
    
        return sh;
    }
    
    //2.获取核心类中的底层实例对应的强引用指针
    SProtocol Ele::GetUDPProtocol(){
    /*    if(pUdpProtocol.expired()){     //只是一个检查,看看依赖的强引用指针还有没有
            return NULL;
        }*/
        return pUdpProtocol.lock();     //.lock()函数获取对应的强引用指针
    }
    
    //3.释放核心类时候,需要做的底层释放
    void Ele::FreeEle(){
        if(!pUdpProtocol.expired()){
            SProtocol p = pUdpProtocol.lock();
            for(auto iter = sProtocols.begin();iter!=sProtocols.end();iter++){
                if(iter->get()==p.get()){   //指向的是同一块底层解构体
                    sProtocols.erase(iter);  //从容器中把强引用拿出来,从此底层就可以随时被清除了,相当于把他的根给坎了
                    break;
                }
            }
            log_Debug("FreeEle:: after erase(if exist) the origin share pUdpProtocol,now the use_count is:%d",pUdpProtocol.use_count());
        }
    }
    
    

    3. 附底层类和其子类,所创建的底层存储就是这些类型的实例:

    class UDPProtocol{
    public:
    
    mutex mutex_protocol;
    UDPProtocol(int pType) : protocolType{PROXY_PROTOCOL_NAME[pType]}{}
    UDPProtocol() : protocolType{PROXY_PROTOCOL_NAME[PROXY_PROTOCOL_UDP]}{}
    ~UDPProtocol(){}
    
    };
    
    class SIPProtocol : public UDPProtocol{
    public:
    SIPProtocol():UDPProtocol(PROXY_PROTOCOL_SIP),msg(NULL){}
    ~SIPProtocol(){UACMap.clear(); if(msg)FreeSIPMsg();}
    mutex mutex_protocol;
    
    };

    4. 在由多态的场景下,智能指针的强转用法如下

    Ele ingress;
    shared_ptr<SIPProtocol> sip;    //首先声明子类的强引用指针
    
    SProtocol sp=ingress.GetUDPProtocol();     //获取到父类的强引用指针
    sip=dynamic_pointer_cast<SIPProtocol>(sp);  //强转得到子类的强引用指针
    
    ingress.NewUDPProtocol(APP_PROTOCOL_SIP);
  • 相关阅读:
    关于前端基础框架的思考和尝试
    通过当前IP获取当前网卡的MAC地址
    shell及脚本2——shell 环境及命令
    shell及脚本1——变量
    linux显示git commit id,同时解决insmod模块时版本不一致导致无法加载问题
    大于16MB的QSPI存放程序引起的ZYNQ重启风险
    insmod模块的几种常见错误
    shell及脚本3——正则表达式
    修改/etc/profile和/etc/environment导致图形界面无法登陆的问题
    Sql 2008的merge关键字
  • 原文地址:https://www.cnblogs.com/shuiguizi/p/11601794.html
Copyright © 2011-2022 走看看