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);
  • 相关阅读:
    Nginx 部署多个 web 项目(虚拟主机)
    Nginx 配置文件
    Linux 安装 nginx
    Linux 安装 tomcat
    Linux 安装 Mysql 5.7.23
    Linux 安装 jdk8
    Linux 安装 lrzsz,使用 rz、sz 上传下载文件
    springMVC 拦截器
    spring 事务
    基于Aspectj 注解实现 spring AOP
  • 原文地址:https://www.cnblogs.com/shuiguizi/p/11601794.html
Copyright © 2011-2022 走看看