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的继承关系
不难发现,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的私有化,不得不佩服还有这种写法,恕我愚钝了,微软的写法值得我们学习。
希望大家有所收获。
本文难免有所错误,如有问题欢迎留言
- com智能指针实现原理
- 请谈谈什么是智能指针,com实现的原理 zz
- 智能指针实现及原理
- 智能指针原理,并实现一个简单的智能指针
- 智能指针设计原理以及实现
- 智能指针的原理及实现方案
- 智能指针的原理及实现方案
- 智能指针的实现及原理
- 智能指针的原理及实现方案
- 智能指针的原理及实现
- 智能指针(一):STL auto_ptr实现原理
- 智能指针(二):shared_ptr实现原理
- 智能指针的实现和原理
- 智能指针的实现及原理
- 智能指针的实现及原理
- 智能指针(一):STL auto_ptr实现原理
- 智能指针(二):shared_ptr实现原理
- 智能指针的原理及实现
- 这16个数据可视化案例,惊艳了全球数据行业
- 浅谈XSS
- Spring事务配置及事务的传播性与隔离级别详解
- Max Sum题解
- struts2_2文件上传与下载
- com智能指针实现原理
- MySQL DBA小萌新成长记
- JDBC连上oracle的方法
- 每天一道算法题——
- TOJ 5135: 连续非递减子序列
- undefined reference to '_modsi3'和`__udivdi3'
- 不使用库函数,求一个整数的n次幂(n为整数)
- 「Unity3D」(8)Rigidbody2D卡顿问题和重心旋转模拟
- Linux系统选择