第二讲:计数引用AddRef与Release
2013-02-22 17:45
281 查看
来自: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、引用计数的原理
•引用计数技术就是用来管理对象生命期的一种技术。
•对象O可能同时被外界A,外界B,外界C引用。也就是说外界A,外界B,外界C可能都在使用对象O。
•每次当对象被外界引用时,计数器就自增1。
•每次当外界不用对象时,计数器就自减1。
•在计数值为零时,对象本身执行delete this,销毁自己的资源。
•引用计数使得对象通过计数能够知道何时对象不再被使用,然后及时地删除自身所占的内存资源。
•IUnknown接口的AddRef与Release就是引用计数的实现方法。
3、AddRef与Release的实现
•查看Section2Demo1关于AddRef与Release的实现。
#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
{
//查询
4000
不到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的意义与使用
•Release,使组件引用计数自减1,如果引用计数为零,释放本身的内存资源。
•在接口使用完之后,调用Release。
•查看Section2Demo1,关于AddRef与Release的使用,并适当注释一些AddRef或Release查看CA的析构函数是否运行或会不会出现对无效指针(野指针)的操作。
4、引用计数的优化
•刚才的例子中,我们看到了
if (SUCCEEDED(hr))
{
IX *pIX3 = NULL;
pIX3 = pIX2;
pIX3->AddRef();
pIX3->Fx1();
pIX3->Fx2();
pIX3->Release();
pIX3 = NULL;
}
•对于pIX2与pIX3来说,都是同一个接口,生命期是一样的,这个接口在一个块({})中,执行了一次的AddRef,一次的Release,其实就相当于没有执行AddRef,与Release的效果。是否可以优化为下一页的代码呢?
if (SUCCEEDED(hr))
{
IX *pIX3 = NULL;
pIX3 = pIX2;
pIX3->Fx1();
pIX3->Fx2();
}
•这种优化可行吗?答案是可行的!因为这种优化符合了引用计数优化的“局部变量原则”
•引用计数的优化原则:
一、输入参数原则:
输入参数指的是给函数传递某个值的参数。在函数体中将会使用这个值但却不会修改它或将其返回给调用者。在C++中,输入参数实际上就是那些按值传递的参数。
对传入函数的接口指针,无需调用AddRef与Release
二、局部变量原则
对于局部复制的接口指针,由于它们只是在函数的生命期内才存在,因此无需调用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接口而已。
•引用计数,带来了高效的内存资源管理方法,能及时地释放不再使用的资源。但却带来了编码的麻烦。在后续的讲解中,会讲到对引用计数的封装,也就是智能指针,到时组件的客户不再编写AddRef与Release代码,也不需要编写delete代码,便可以方便,舒心地进行内存资源的管理。
本节内容:
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、引用计数的原理
•引用计数技术就是用来管理对象生命期的一种技术。
•对象O可能同时被外界A,外界B,外界C引用。也就是说外界A,外界B,外界C可能都在使用对象O。
•每次当对象被外界引用时,计数器就自增1。
•每次当外界不用对象时,计数器就自减1。
•在计数值为零时,对象本身执行delete this,销毁自己的资源。
•引用计数使得对象通过计数能够知道何时对象不再被使用,然后及时地删除自身所占的内存资源。
•IUnknown接口的AddRef与Release就是引用计数的实现方法。
3、AddRef与Release的实现
•查看Section2Demo1关于AddRef与Release的实现。
#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
{
//查询
4000
不到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的意义与使用
•Release,使组件引用计数自减1,如果引用计数为零,释放本身的内存资源。
•在接口使用完之后,调用Release。
•查看Section2Demo1,关于AddRef与Release的使用,并适当注释一些AddRef或Release查看CA的析构函数是否运行或会不会出现对无效指针(野指针)的操作。
4、引用计数的优化
•刚才的例子中,我们看到了
if (SUCCEEDED(hr))
{
IX *pIX3 = NULL;
pIX3 = pIX2;
pIX3->AddRef();
pIX3->Fx1();
pIX3->Fx2();
pIX3->Release();
pIX3 = NULL;
}
•对于pIX2与pIX3来说,都是同一个接口,生命期是一样的,这个接口在一个块({})中,执行了一次的AddRef,一次的Release,其实就相当于没有执行AddRef,与Release的效果。是否可以优化为下一页的代码呢?
if (SUCCEEDED(hr))
{
IX *pIX3 = NULL;
pIX3 = pIX2;
pIX3->Fx1();
pIX3->Fx2();
}
•这种优化可行吗?答案是可行的!因为这种优化符合了引用计数优化的“局部变量原则”
•引用计数的优化原则:
一、输入参数原则:
输入参数指的是给函数传递某个值的参数。在函数体中将会使用这个值但却不会修改它或将其返回给调用者。在C++中,输入参数实际上就是那些按值传递的参数。
对传入函数的接口指针,无需调用AddRef与Release
二、局部变量原则
对于局部复制的接口指针,由于它们只是在函数的生命期内才存在,因此无需调用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接口而已。
•引用计数,带来了高效的内存资源管理方法,能及时地释放不再使用的资源。但却带来了编码的麻烦。在后续的讲解中,会讲到对引用计数的封装,也就是智能指针,到时组件的客户不再编写AddRef与Release代码,也不需要编写delete代码,便可以方便,舒心地进行内存资源的管理。
相关文章推荐
- 引用计数和AddRef、Release
- 引用计数和AddRef、Release
- 引用计数和AddRef、Release
- 引用计数和AddRef、Release
- AddRef和Release 引用计数
- 《COM技术内幕》代码之 引用计数的实现,AddRef,Release
- COM 的引用计数规则 AddRef/Release 规则
- 内存管理4Aotorelease自动引用计数
- 《Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)》读后感
- Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)
- cocos2d-x-3.3rc2-004 cocos中的引用计数和自动释放池-ReleasePoolTest
- 引用计数 内存管理 Ref AutoreleasePoo PoolManager
- Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)
- Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)
- [Cocos2D-X官方文档]:Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)
- (20)Cocos2d-x中的引用计数(Reference Count)和自动释放池(AutoReleasePool)
- bookone这个书籍对象被我release掉了,此时引用计数为0,为毛还能调用
- 内存管理4Aotorelease自动引用计数
- [cocos2d-x3.x学习笔记]引用计数 内存管理 Ref AutoreleasePoo PoolManager
- iOS引用计数