com智能指针实现原理

来源:互联网 发布:mysql使用教程 编辑:程序博客网 时间:2024/05/17 07:36

智能指针的出现解决了对内存的释放问题,我们了解它的内存管理机制有利于我们理解C++、boost中的智能指针以及其他例如cocos中的内存管理。

目录
一、引用计数的原理
二、引用计数的实现与使用
三、引用计数的优化
四、智能指针的实现原理与使用

一、引用计数的原理

困扰C++或者C语言的就是资源释放的问题,编程中往往会一不小心就导致内存泄漏,所以我们必须解决内存资源何时释放的问题,通过引用计数进行内存管理。

首先我们需要了解内存资源何时释放?
我们通常的做法是在使用完一个对象后,在函数的最后执行delete,这样确实能释放资源。但是这里会有一个是否“及时”释放的问题,如果一个程序,例如在一个游戏中所有的资源在不用时都没有及时释放,这个程序在运行中所占用的内存将是巨大的,玩家的感受就是很卡。自然,就需要我们的引用计数技术,引用计数技术就是用来管理对象生命期的一种技术。

引用计数的原理
- 对象O可能同时被外界A,外界B,外界C引用。也就是说外界A,外界B,外界C可能都在使用对象O。
- 每次当对象被外界引用时,计数器就自增1。
- 每次当外界不用对象时,计数器就自减1。
- 在计数值为零时,对象本身执行delete this,销毁自己的资源
- 引用计数使得对象通过计数能够知道何时对象不再被使用,然后及时地删除自身所占的内存资源。
- IUnknown接口的AddRef与Release就是引用计数的实现方法。

二、引用计数的实现与使用

#include <iostream>#include <Unknwn.h>#include <atlcomcli.h>using namespace std;// {D16ACAA2-0C01-4673-97CB-FB941C1D48A1}static const IID IID_IX ={ 0xd16acaa2, 0xc01, 0x4673,{ 0x97, 0xcb, 0xfb, 0x94, 0x1c, 0x1d, 0x48, 0xa1 } };// {A5802CE5-C507-44C5-8CB6-5F5D8BC78CFC}static const IID IID_IY ={ 0xa5802ce5, 0xc507, 0x44c5,{ 0x8c, 0xb6, 0x5f, 0x5d, 0x8b, 0xc7, 0x8c, 0xfc } };// 接口 IXinterface IX : public IUnknown{    virtual void Fx() = 0;};// 接口 IYinterface IY : public IUnknown{    virtual void Fy() = 0;};// 组件 CComclass CCom : public IX, public IY{public:    CCom()    {        m_ulCount = 0;        AddRef();    }    ~CCom()    {        cout << "CCom 析构函数被调用" << endl;    }    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)    {        if (riid == IID_IUnknown)        {            //*ppvObject = static_cast<IUnknown*>(static_cast<IX*>(this));            *ppvObject = static_cast<IX*>(this);        }        else if (riid == IID_IX)        {            *ppvObject = static_cast<IX*>(this);        }        else if (riid == IID_IY)        {            *ppvObject = static_cast<IY*>(this);        }        else        {            *ppvObject = NULL;            return E_NOINTERFACE;        }        AddRef();// 进行引用计数加一        return S_OK;    }    virtual ULONG STDMETHODCALLTYPE AddRef(void)    {        return InterlockedIncrement(&m_ulCount);    }    virtual ULONG STDMETHODCALLTYPE Release(void)    {        if (InterlockedDecrement(&m_ulCount) == 0)        {            delete this;            return 0;        }        return m_ulCount;    }    virtual void Fx() override    {        cout << "This is Fx()" << endl;    }    virtual void Fy() override    {        cout << "This is Fy()" << endl;    }private:    ULONG m_ulCount;// 记录引用的次数};int main(){    HRESULT hr = 0;    // 创建组件    CCom* pCom = new CCom;    // 从组件查找接口,然后再调用接口实现的功能    IUnknown* pIUnknown = NULL;    hr = pCom->QueryInterface(IID_IUnknown, (void**)&pIUnknown);// 查询 IUnknown 接口    if (SUCCEEDED(hr))    {        pIUnknown->Release();        IX* pIX = NULL;        hr = pCom->QueryInterface(IID_IX, (void**)&pIX);// 查询 IX 接口        if (SUCCEEDED(hr))        {            pIX->Fx();            pIX->Release();// 调用Release进行引用计数减一        }        IY* pIY = NULL;        hr = pCom->QueryInterface(IID_IY, (void**)&pIY);// 查询 IY 接口        if (SUCCEEDED(hr))        {            pIY->Fy();            pIY->Release();// 调用Release进行引用计数减一        }    }    pCom->Release();//  delete pCom;//  pCom = NULL;    return 0;}

主要的实现是实现继承于IUnknown的AddRef和Release接口进行引用计数的管理。
AddRef:对用于引用计数的变量m_ulCount进行原子操作的加一(原子操作防止线程同步时出现错误)。
Release:对用于引用计数的变量m_ulCount进行原子操作的减一,当引用计数为0时,就调用delete this;摧毁自己,实现对资源的释放。

对于引用计数砈使用:在声明组件对象的时候,调用构造函数进行引用计数加一。调用QueryInterface来查询接口时,如果查询成功进行引用计数加一,如果不使用是调用Release进行引用计数减一,在最后调用Release时,引用计数为0释放资源。

三、引用计数的优化

引用计数的优化原则
1、输入参数原则
- 输入参数指的是给函数传递某个值的参数。在函数体中将会使用这个值但却不会修改它或将其返回给调用者。在C++中,输入参数实际上就是那些按值传递的参数。
- 对传入函数的接口指针,无需调用AddRef与Release
2、局部变量原则
- 对于局部复制的接口指针,由于它们只是在函数的生命期内才存在,因此无需调用AddRef与Release

输入参数原则

void Fun(IX *pIXParam)   //参数传递存在赋值过程{        //pIXParam->AddRef();   //可优化,注释掉        pIXParam->Fx1();        pIXParam->Fx2();        //pIXParam->Release();  //可优化,注释掉}void main(){    ...    Fun(pIX);    ...}

在main中调用Fun时,传递的pIX只是进行了按值传递,即函数的参数只是实际参数(注意这里的参数只是一个四字节的地址)的拷贝,pIXParam值与入参pIX址完全相同,而并没有进行对象的引用,所以并不需要AddRef与Release。

局部变量原则

void Fun(IX *pIX){        IX *pIX2 = pIX;        //pIX2->AddRef();   //可优化,注释掉        pIX2->Fx1();        pIX2->Fx2();        //pIX2->Release();  //可优化,注释掉}

函数运行在堆栈上,在我们调用函数的时候开始压栈,同时对局部变量申请栈空间,在函数执行完毕的时候,进行出栈操作,实现对局部资源的清理(在一定时间里,其实在栈上的某些值是还能存在的,因为出栈后栈空间上的值并没有清零,但是我们不能抱有调用栈上资源的心态)
从上不能理解,我们局部变量的生存周期只是在函数体内,而函数体结束时会自动清理资源,于是我们并不用进行AddRef与Release操作。

四、智能指针的实现原理与使用

此时你可能很疑惑,虽然引用计数带来了高效的内存资源管理方法,能及时地释放不再使用的资源。但却带来了编码的麻烦,如果我哪一次忘记了调用Release,岂不是同样会造成内存泄漏,而且每次都还要调用Release,真是麻烦。不用着急,前辈们帮我们解决了这个问题,那就是智能指针。

我们先来看看CComPtr的实现

template <class T>class CComPtrBase{protected:    CComPtrBase() throw()    {        p = NULL;    }    CComPtrBase(_Inout_opt_ T* lp) throw()    {        p = lp;        if (p != NULL)            p->AddRef();    }    void Swap(CComPtrBase& other)    {        T* pTemp = p;        p = other.p;        other.p = pTemp;    }public:    typedef T _PtrClass;    ~CComPtrBase() throw()    {        if (p)            p->Release();    }    operator T*() const throw()    {        return p;    }    T& operator*() const    {        ATLENSURE(p!=NULL);        return *p;    }    T** operator&() throw()    {        ATLASSERT(p==NULL);        return &p;    }    _NoAddRefReleaseOnCComPtr<T>* operator->() const throw()    {        ATLASSERT(p!=NULL);        return (_NoAddRefReleaseOnCComPtr<T>*)p;    }    bool operator!() const throw()    {           return (p == NULL);    }    bool operator<(_In_opt_ T* pT) const throw()    {        return p < pT;    }    bool operator!=(_In_opt_ T* pT) const    {        return !operator==(pT);    }    bool operator==(_In_opt_ T* pT) const throw()    {        return p == pT;    }    // Release the interface and set to NULL    void Release() throw()    {        T* pTemp = p;        if (pTemp)        {            p = NULL;            pTemp->Release();        }    }    // Compare two objects for equivalence    bool IsEqualObject(_Inout_opt_ IUnknown* pOther) throw()    {        if (p == NULL && pOther == NULL)            return true;    // They are both NULL objects        if (p == NULL || pOther == NULL)            return false;   // One is NULL the other is not        CComPtr<IUnknown> punk1;        CComPtr<IUnknown> punk2;        p->QueryInterface(__uuidof(IUnknown), (void**)&punk1);        pOther->QueryInterface(__uuidof(IUnknown), (void**)&punk2);        return punk1 == punk2;    }    // Attach to an existing interface (does not AddRef)    void Attach(_In_opt_ T* p2) throw()    {        if (p)        {            ULONG ref = p->Release();            (ref);            ATLASSERT(ref != 0 || p2 != p);        }        p = p2;    }    // Detach the interface (does not Release)    T* Detach() throw()    {        T* pt = p;        p = NULL;        return pt;    }    _Check_return_ HRESULT CopyTo(_COM_Outptr_result_maybenull_ T** ppT) throw()    {        ATLASSERT(ppT != NULL);        if (ppT == NULL)            return E_POINTER;        *ppT = p;        if (p)            p->AddRef();        return S_OK;    }    _Check_return_ HRESULT SetSite(_Inout_opt_ IUnknown* punkParent) throw()    {        return AtlSetChildSite(p, punkParent);    }    _Check_return_ HRESULT Advise(        _Inout_ IUnknown* pUnk,         _In_ const IID& iid,         _Out_ LPDWORD pdw) throw()    {        return AtlAdvise(p, pUnk, iid, pdw);    }    _Check_return_ HRESULT CoCreateInstance(        _In_ REFCLSID rclsid,         _Inout_opt_ LPUNKNOWN pUnkOuter = NULL,         _In_ DWORD dwClsContext = CLSCTX_ALL) throw()    {        ATLASSERT(p == NULL);        return ::CoCreateInstance(rclsid, pUnkOuter, dwClsContext, __uuidof(T), (void**)&p);    }#ifdef _ATL_USE_WINAPI_FAMILY_DESKTOP_APP    _Check_return_ HRESULT CoCreateInstance(        _In_z_ LPCOLESTR szProgID,         _Inout_opt_ LPUNKNOWN pUnkOuter = NULL,         _In_ DWORD dwClsContext = CLSCTX_ALL) throw()    {        CLSID clsid;        HRESULT hr = CLSIDFromProgID(szProgID, &clsid);        ATLASSERT(p == NULL);        if (SUCCEEDED(hr))            hr = ::CoCreateInstance(clsid, pUnkOuter, dwClsContext, __uuidof(T), (void**)&p);        return hr;    }#endif // _ATL_USE_WINAPI_FAMILY_DESKTOP_APP    template <class Q>    _Check_return_ HRESULT QueryInterface(_Outptr_ Q** pp) const throw()    {        ATLASSERT(pp != NULL);        return p->QueryInterface(__uuidof(Q), (void**)pp);    }    T* p;};template <class T>class CComPtr :     public CComPtrBase<T>{public:    CComPtr() throw()    {    }    CComPtr(_Inout_opt_ T* lp) throw() :        CComPtrBase<T>(lp)    {    }    CComPtr(_Inout_ const CComPtr<T>& lp) throw() :        CComPtrBase<T>(lp.p)    {       }    T* operator=(_Inout_opt_ T* lp) throw()    {        if(*this!=lp)        {            CComPtr(lp).Swap(*this);        }        return *this;    }    template <typename Q>    T* operator=(_Inout_ const CComPtr<Q>& lp) throw()    {        if( !IsEqualObject(lp) )        {            return static_cast<T*>(AtlComQIPtrAssign((IUnknown**)&p, lp, __uuidof(T)));        }        return *this;    }    T* operator=(_Inout_ const CComPtr<T>& lp) throw()    {        if(*this!=lp)        {            CComPtr(lp).Swap(*this);        }        return *this;    }       CComPtr(_Inout_ CComPtr<T>&& lp) throw() :          CComPtrBase<T>()    {           lp.Swap(*this);    }       T* operator=(_Inout_ CComPtr<T>&& lp) throw()    {                   if (*this != lp)        {            CComPtr(static_cast<CComPtr&&>(lp)).Swap(*this);        }        return *this;           }};

CComPtr的继承关系
CCom
不难发现,CComPtr智能指针类中,其实就是维护了我们的对象指针(T* p;),类中对原始指针的->等操作符都进行了相应运算符重载,同时也封装了一些增强的功能,例如IsEqualObject用于判断是否为同一个对象等。如果我们需要原始指针,通过CCom* pCom = pComPtr;直接赋值就能获得。

当我们使用智能指针时,在构造函数中,父类CComPtrBase进行构造时会自动调用一次AddRef,CComPtrBase析构的时候调用一次Release,引用计数减一。这个过程并不需要我们自己去调用Release来进行引用计数的减一。

我们还是来切身体会下智能指针的使用

#include <iostream>#include <Unknwn.h>#include <atlcomcli.h>using namespace std;// {D16ACAA2-0C01-4673-97CB-FB941C1D48A1}static const IID IID_IX ={ 0xd16acaa2, 0xc01, 0x4673,{ 0x97, 0xcb, 0xfb, 0x94, 0x1c, 0x1d, 0x48, 0xa1 } };// {A5802CE5-C507-44C5-8CB6-5F5D8BC78CFC}static const IID IID_IY ={ 0xa5802ce5, 0xc507, 0x44c5,{ 0x8c, 0xb6, 0x5f, 0x5d, 0x8b, 0xc7, 0x8c, 0xfc } };// 接口 IXinterface IX : public IUnknown{    virtual void Fx1() = 0;    virtual void Fx2() = 0;};// 接口 IYinterface IY : public IUnknown{    virtual void Fy1() = 0;    virtual void Fy2() = 0;};// 组件 CComclass CCom : public IX, public IY{public:    CCom()    {        m_ulCount = 0;        //AddRef();    }    ~CCom()    {        cout << "CCom 析构函数被调用" << endl;    }    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)    {        if (riid == IID_IUnknown)        {            *ppvObject = static_cast<IX*>(this);        }        else if (riid == IID_IX)        {            *ppvObject = static_cast<IX*>(this);        }        else if (riid == IID_IY)        {            *ppvObject = static_cast<IY*>(this);        }        else        {            *ppvObject = NULL;            return E_NOINTERFACE;        }        AddRef();// 进行引用计数加一        return S_OK;    }    virtual ULONG STDMETHODCALLTYPE AddRef(void)    {        return InterlockedIncrement(&m_ulCount);    }    virtual ULONG STDMETHODCALLTYPE Release(void)    {        if (InterlockedDecrement(&m_ulCount) == 0)        {            delete this;            return 0;        }        return m_ulCount;    }    virtual void Fx1() override    {        cout << "This is Fx1()" << endl;    }    virtual void Fx2() override    {        cout << "This is Fx2()" << endl;    }    virtual void Fy1() override    {        cout << "This is Fy1()" << endl;    }    virtual void Fy2() override    {        cout << "This is Fy2()" << endl;    }private:    ULONG m_ulCount;};template <class T>class NoAddRelease :public T  {    virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;    virtual ULONG STDMETHODCALLTYPE Release(void) = 0;};void Test_CComPtr(){    HRESULT hr = 0;    //CCom*p = new CCom;    //((NoAddRelease<CCom>*)p)->Release();    // 创建组件    //CComPtr<CCom> pComPtr = CComPtr<CCom>(new CCom);    //CComPtr<CCom> pComPtr = new CCom;    CComPtr<CCom> pComPtr(new CCom);    // 从组件查找接口,然后再调用接口实现的功能    CComPtr<IUnknown> pIUnknownPtr = NULL;    hr = pComPtr->QueryInterface(IID_IUnknown, (void**)&pIUnknownPtr);// 查询 IUnknown 接口    if (SUCCEEDED(hr))    {        CComPtr<IX> pIXPtr = NULL;        hr = pComPtr->QueryInterface(IID_IX, (void**)&pIXPtr);// 查询 IX 接口        if (SUCCEEDED(hr))        {            pIXPtr->Fx1();            pIXPtr->Fx2();        }        // 查询 IY 接口        CComPtr<IY> pIYPtr = NULL;        hr = pComPtr->QueryInterface(IID_IY, (void**)&pIYPtr);        if (SUCCEEDED(hr))        {            pIYPtr->Fy1();            pIYPtr->Fy2();        }    }}int main(){    Test_CComPtr();    return 0;}

注意:我们这里上面程序的不同,在构造函数里面这里并没有实现AddRef,因为声明智能指针时它会自动调用AddRef帮我们进行引用计数加一。

pComPtr声明的时候,引用计数加一,在函数结束的时候智能指针pComPtr进行析构,调用引用计数减一。
使用QueryInterface查询IUnknown接口的时候,引用计数加一,在函数结束的时候智能指针pIUnknownPtr进行析构,调用引用计数减一。
使用QueryInterface查询IX接口的时候,引用计数加一,if语句结束的时候智能指针pIXPtr进行析构,调用引用计数减一。
使用QueryInterface查询IY接口的时候,引用计数加一,if语句结束的时候智能指针pIYPtr进行析构,调用引用计数减一。

这整个过程并不需要我们去释放资源,在函数结束的时候,引用计数会减为0,自动帮我们释放资源。

聪明的你一定发现了一些问题
此时会不会出现用户突然自己调用了Release的情况呢?当然不会,因为Release和AddRef都是私有的。初次我是在网上看到了,但是没有说明原因,随后怀着对于实现原理的好奇,我跟进去看了下实现,发现智能指针CComPtr类中实现的CCom类中Release和AddRef是属于public,而且我们实现的CCom类中Release和AddRef是也是属于public啊,有点蒙圈,那么这是怎么回事的呢?

pComPtr->Release();会调用智能指针类中的->运算符,问题必然是在->运算符中

_NoAddRefReleaseOnCComPtr<T>* operator->() const throw(){    ATLASSERT(p!=NULL);    return (_NoAddRefReleaseOnCComPtr<T>*)p;}

这里调用了一个模板类_NoAddRefReleaseOnCComPtr进行强制转换。而Release和AddRef实现私有化就是该类的作用,我们接下来看下_NoAddRefReleaseOnCComPtr高明的实现

template <class T>class _NoAddRefReleaseOnCComPtr :     public T{    private:        STDMETHOD_(ULONG, AddRef)()=0;        STDMETHOD_(ULONG, Release)()=0;};

就是在该类中实现了对Release和AddRef的私有化,不得不佩服还有这种写法,恕我愚钝了,微软的写法值得我们学习。

希望大家有所收获。
本文难免有所错误,如有问题欢迎留言

原创粉丝点击