来自:http://www.vckbase.com/index.php/video/listview/fid/2/sid/13
本节内容:
1、内存资源何时释放
2、引用计数的原理
3、AddRef与Release的实现与使用
4、引用计数的优化
1、内存资源何时释放
在上一节的例子中,我们用到了new CA()与delete pA。我们知道我们创建了一个组件,最终在不用这个组件的时候,是应该把它销毁了。不过,什么时候才是“不用这个组件的时候”呢?
假设上例中,我们可能会把pIUnknown传给CB类的成员变量。如:
if(...) //可能为真,也可能为假
{
CB *pB =new CB(pIUnknown);
}
我们怎么知道何时我们不会再用到这个pA所指向的组件?当然,你可能会回答“在主函数的最后面执行delete pA,因为那时pA一定不用了。”
在主函数的最后面执行delete pA确实是一个可行的办法。但却不是好办法。因为这样子最终是释放了pA的内存资源,不过却不是“及时”(在pA所指的组件不用时)地释放内存资源。如果一个程序,所有的资源在不用时都没有及时释放,这个程序在运行中所占用的内存将是巨大的。如何解决这个问题呢?这就需要引用计数技术。
2、引用计数的原理
#include "stdafx.h"
#include <iostream>
#include <Unknwn.h>
using namespace std;
// {A348FBDD-E765-4b41-8477-6D8B7038FCC6}
static const IID IID_IX =
{ 0xa348fbdd, 0xe765, 0x4b41, { 0x84, 0x77, 0x6d, 0x8b, 0x70, 0x38, 0xfc, 0xc6 } };
// {10A90ED2-FCDE-4067-92DA-ABA38F5C1B12}
static const IID IID_IY =
{ 0x10a90ed2, 0xfcde, 0x4067, { 0x92, 0xda, 0xab, 0xa3, 0x8f, 0x5c, 0x1b, 0x12 } };
//接口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;
};
//组件CA
class CA: public IX, public IY
{
//构造与析构
public:
CA()
{
m_lCount = 0;
//构造时,需要自增引用计数
AddRef();
}
virtual ~CA() //析构函数一般采用虚函数
{
cout << "我被释放啦!" << endl;
}
//实现
public:
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppv)
{
//查看PPT中CA的内存结构讲解如下的转换过程
if (iid == IID_IUnknown)
{
//即使CA继承了两个IUnknown接口,其中一个来自于IX,另一个来自于IY。我们一般返回第一个被继承的IX接口。
*ppv = static_cast<IX*>(this);
}
else if (iid == IID_IX)
{
//返回IX接口
*ppv = static_cast<IX*>(this);
}
else if (iid == IID_IY)
{
//返回IY接口
*ppv = static_cast<IY*>(this);
}
else
{
//查询不到IID,*ppv返回NULL。
*ppv = NULL;
return E_NOINTERFACE; //函数返回值返回E_NOINTERFACE,表示组件不支持iid的接口。
}
//查询成功时,需要自增引用计数
AddRef();
return S_OK; //返回S_OK
}
virtual ULONG STDMETHODCALLTYPE AddRef()
{
//简单实现方法
return ++m_lCount;
//多线程编程采用如下方法,这种方法确保同一个时刻只会有一个线程来访问成员变量
//return InterlockedIncrement(&m_lCount);
}
virtual ULONG STDMETHODCALLTYPE Release()
{
//简单实现方法
if (--m_lCount == 0)
{
delete this; //销毁自己
return 0;
}
return m_lCount;
多线程编程采用如下方法,这种方法确保同一个时刻只会有一个线程来访问成员变量
//if (InterlockedDecrement(&m_lCount) == 0)
//{
// delete this; //销毁自己
// return 0;
//}
//return m_lCount;
}
virtual void Fx1()
{
cout << "Fx1" << endl;
}
virtual void Fx2()
{
cout << "Fx2" << endl;
}
virtual void Fy1()
{
cout << "Fy1" << endl;
}
virtual void Fy2()
{
cout << "Fy2" << endl;
}
//数据
private:
long m_lCount; //引用计数,该计数只被该类管理,外界不可访问,访问权限设置为private
};
int main()
{
HRESULT hr;
CA *pA = new CA(); //引用计数1
//从组件查询IUnknown接口
IUnknown *pIUnknown = NULL;
hr = pA->QueryInterface(IID_IUnknown, (void**)&pIUnknown); //引用计数2
if (SUCCEEDED(hr)) //对HRESULT返回值的判断,一般采用SUCCEEDED
{
pA->Release(); //pA不再使用,引用计数1
pA = NULL; //访止再不小心使用m_pA
//从IUnknown查询IX接口
IX *pIX = NULL;
hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX); //引用计数2
if (SUCCEEDED(hr))
{
//调用IX接口的方法
pIX->Fx1();
pIX->Fx2();
}
//从IUnknown查询IY接口
IY *pIY = NULL;
hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY); //引用计数3
if (SUCCEEDED(hr))
{
//调用IY接口的方法
pIY->Fy1();
pIY->Fy1();
}
if ((void*)pIX != (void*)pIY)
{
cout << "pIX != pIY" <<endl;
}
if ((void*)pIUnknown != (void*)pIY)
{
cout << "pIUnknown != pIY" <<endl;
}
pIY->Release(); //pIY不再使用,引用计数2
pIY = NULL;
if ((void*)pIUnknown == (void*)pIX)
{
cout << "pIUnknown == pIX" <<endl;
}
//从IX查询IY
IY *pIY2 = NULL;
hr = pIX->QueryInterface(IID_IY, (void**)&pIY2); //引用计数,引用计数3
pIX->Release(); //pIX不再使用,引用计数2
pIX = NULL;
if (SUCCEEDED(hr))
{
pIY2->Fy1();
pIY2->Fy2();
}
pIY2->Release(); //pIY不再使用,引用计数1
pIY2 = NULL;
}
//目前引用计数为1,因为pIUnknown还在使用。
IX *pIX2 = NULL;
hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX2); //引用计数为2
if (SUCCEEDED(hr))
{
IX *pIX3 = NULL;
pIX3 = pIX2; //执行了赋值
pIX3->AddRef(); //由于上句执行了赋值,所以引用计数需要自增,引用计数为3
pIX3->Fx1();
pIX3->Fx2();
pIX3->Release(); //pIX3不再使用,引用计数为2
pIX3 = NULL;
}
pIX2->Release(); //pIX2不再使用,引用计数为1
pIX2 = NULL;
pIUnknown->Release(); //pIUnknown不再使用,引用计数为0,Release函数里执行了delete this,销毁组件的内存资源
pIUnknown = NULL;
//释放组件? no!
//delete pA; //不再需要写delete代码
return 0;
}
Release的意义与使用
4、引用计数的优化
if (SUCCEEDED(hr))
{
IX *pIX3 = NULL;
pIX3 = pIX2;
pIX3->AddRef();
pIX3->Fx1();
pIX3->Fx2();
pIX3->Release();
pIX3 = NULL;
}
if (SUCCEEDED(hr))
{
IX *pIX3 = NULL;
pIX3 = pIX2;
pIX3->Fx1();
pIX3->Fx2();
}
一、输入参数原则:
二、局部变量原则
对于局部复制的接口指针,由于它们只是在函数的生命期内才存在,因此无需调用AddRef与Release
void Fun(IX *pIXParam) //参数传递存在赋值过程
{
//pIXParam->AddRef(); //可优化,注释掉
pIXParam->Fx1();
pIXParam->Fx2();
//pIXParam->Release(); //可优化,注释掉
}
void Fun(IX *pIX)
{
IX*pIX2 = pIX;
//pIX2->AddRef(); //可优化,注释掉
pIX2->Fx1();
pIX2->Fx2();
//pIX2->Release(); //可优化,注释掉
}
void Fun(IX **ppIX)
{
(*ppIX)->Fx1();
(*ppIX)->Fx2();
(*ppIX)->Release(); //可以优化吗?
*ppIX = m_pIXOther;
(*ppIX)->AddRef(); //可以优化吗?
(*ppIX)->Fx1();
(*ppIX)->Fx2();
}
//以上两句务必要运行,因为*ppIX与m_pIXOther不一个属性同一个组件。
//比如假设*ppIX是指向第一次的new CA(),而m_pIXOther却是指向第二次的new CA()。
//或者*ppIX是指向new CA(),而m_pIXOther是指向new CZ(),CA与CZ的共同点,只是都继承了IX接口而已。