com智能指针实现原理

智能指针的出现解决了对内存的释放问题,我们了解它的内存管理机制有利于我们理解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 } };

// 接口 IX
interface IX : public IUnknown
{
    virtual void Fx() = 0;
};

// 接口 IY
interface IY : public IUnknown
{
    virtual void Fy() = 0;
};

// 组件 CCom
class 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 } };

// 接口 IX
interface IX : public IUnknown
{
    virtual void Fx1() = 0;
    virtual void Fx2() = 0;
};

// 接口 IY
interface IY : public IUnknown
{
    virtual void Fy1() = 0;
    virtual void Fy2() = 0;
};

// 组件 CCom
class 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的私有化,不得不佩服还有这种写法,恕我愚钝了,微软的写法值得我们学习。

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

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发表。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值