您的位置:首页 > 其它

第二讲:计数引用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代码,便可以方便,舒心地进行内存资源的管理。
 

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: