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);

 

posted @ 2020-03-21 18:32  水鬼子  阅读(997)  评论(0编辑  收藏  举报