词条 | 引用计数 |
释义 | 简介在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。 最直观的垃圾收集策略是引用计数。引用计数很简单,但是需要编译器的重要配合,并且增加了赋值函数 (mutator) 的开销(这个术语是针对用户程序的,是从垃圾收集器的角度来看的)。每一个对象都有一个关联的引用计数 —— 对该对象的活跃引用的数量。如果对象的引用计数是零,那么它就是垃圾(用户程序不可到达它),并可以回收。每次修改指针引用时(比如通过赋值语句),或者当引用超出范围时,编译器必须生成代码以更新引用的对象的引用计数。如果对象的引用计数变为零,那么运行时就可以立即收回这个块(并且减少被回收的块所引用的所有块的引用计数),或者将它放到迟延收集队列中。 com组件将维护一个称作是引用计数的数值。当客户从组件取得一个接口时,此引用计数值将增1。当客户使用完某个接口后,组件的引用计数值将减1.当引用计数值为0时,组件即可将自己从内存中删除。 引用计数的使用原因为什么要选择为每一个接口单独维护一个引用计数而不是针对整个组件维护引用计数呢? 主要有两个原因:一是使程序调试更为方便;另外一个原因是支持资源的按需获取。 1程序调试: 假设在程序中忘记对某个接口调用Releae(其实很多人会犯这个错)。这样组件将永远不会被删除掉,因为只是在引用计数值0时delete才会被调用 。这时就需要找出接口在何时何处应该被释放掉。当然找起来是相当困难的。在只对整个组件维护一个接口的情况下,进行这种 查找更为因难了。此时必须检查使用了此组件所提供的所有接口的代码。但若组件支持对每个接口分别维护一个引用计数那么可以把查找的范围限制在某个特定的接口上。在某些情况下这可以节省大量时间。 2.资源的按需获取 在实现某个接口时可能需要大量的内存或其他资源。对于此种情况,可以在QueryInterface的实现中,在客户请求此接口时完成资源的分配。但若只对整个组件维护一个引用计数,组件将无法决定何时可以安全地将此些接口相关联的内存释放。但基对每个接口分别维护一个引用计数,那么决定何时可以将此内存释放将会容易得多。 规则正确使用引用计数三条简单的规则 1. 在返回之前调用AddRef。对于那些建好些返回接口指针的函数,在返回之前应该相应的指针调用AddRef。这些函数包括QueryInterface 及CreateInstance。这样当客户从这种 函数得到一个接口后。它将无需调用AddRef. 2.使用完接口之后调用Release。在使用某个接口之后应该调用些接口的Release函数。 3.在赋值之后调用AddRef. 在将一个接口指针赋给另一个接口指针时,应调用AddRef。换句话说,在建立接口的别外一个引用之后应增加相应组件的引用计数。 例1(针对第一二规则): // 创建一个组件 IUnknown* pIUnknown = CreateInstance(); // 获取接口IX IX* pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX); // pIUnknown用完了,可以把计数减掉 pIUnknown->Release(); if (SUCCEEDED(hr)) { pIX->Fx(); // 使用接口pIX pIX->Release(); // 用完释放引用计数 } 例2(针对第三规则): // 创建一个组件 IUnknown* pIUnknown = CreateInstance(); // 获取接口IX IX* pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX); // pIUnknown用完了,可以把计数减掉 pIUnknown->Release(); if (SUCCEEDED(hr)) { pIX->Fx(); // 使用接口pIX IX* pIX2 = pIX; // 复制接口pIX pIX2->AddRef(); // 增加一个引用计数 pIX2->Fx(); // 使用接口pIX2 pIX2->Release(); // 用完释放引用计数 pIX->Release(); // 用完释放引用计数 } 接口在客户看来,引用计数是处于接口级的而不是组件级的。担从实现的角度来看,谁的引用计数被记录下来实际上没有关系。客户可以一直接相信组件将记录每个接口本身维护引用计数值。但客户不能假设整个组件维护单个的引用计数。 对于客户而言,每一个接口被分别维护一个引用计数意味着客户应该对它将要使用的指针调用AddRef,而不是其他的什么指针。对于使用完了指针客户应该调用其Release。 IUnknow* pIUnknown = CreateInstance(); IX* pIX = NULL; pIUnknown->QueryInterface(IID_IX, &pIX); pIX->Fx(); IX* pIX2 = pIx; pIUnknown->AddRef(); // 这里错了,应该是pIX2->AddRef(); pIX2->Fx(); pIX2->Release; pIUnknown->Release(); // 应该是pIX->Release(); pIUnknown->Release(); 选择为每一个接口单独维护一个引用计数而不是针对整个组件维护引用计数的原因: 使程序调试更为方便;支持资源的按需获取; 调试可以通过增大和减少某个数的值而实现之。 另外要注意的是AddRef和Release的返回值没有什么意义,只是在程序调试中才可能会用得上.客户不应将此从此值当成是组件或其接口的精确引用数。 ULONG__stdcallCA::AddRef() { returnInterlockedIncrement(&m_cRef); } ULONG__stdcallCA::Release() { if (InterlockedDecrement(&m_cRef) == 0) { deletethis; return 0; } returnm_cRef; } 优化前面的【正确使用引用计数三条简单的规则】说到“在赋值之后调用AddRef”。 // 创建一个组件 IUnknown* pIUnknown = CreateInstance(); // 获取接口IX IX* pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX); // pIUnknown用完了,可以把计数减掉 pIUnknown->Release(); if (SUCCEEDED(hr)) { IX* pIX2 = pIX; pIX2->AddRef(); pIX->Fx(); pIX2->Fx(); pIX2->Release(); pIX->Release(); } 上面的代码中只有当客户将pIX 释放时组件才会从内存中删除。而客户只是在用完了pIX和pIX2之后才会将pIX释放。正是由于组件只是在pIX被释放后才从内存中删除。因此可以保证在pIX2的生命期内组件一直存在。所以这个时候pIX2可以不用调用AddRef 和Release(当然调了也是对的,只是不调可以提高效率)。因为pIX生命周期包含了pIX2的生命周期。 如果pIX生命周期不包含了pIX2的生命周期,就一定要对pIX2进行引用计数。如下: IUnknown* pIUnknown = CreateInstance(); IX* pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX); pIUnknown->Release(); if (SUCCEEDED(hr)) { IX* pIX2 = pIX; pIX2->AddRef(); pIX->Fx(); pIX->Release(); // pIX生命期结束 pIX2->Fx(); pIX2->Release(); pIX->Release(); } 为对引用计数进行优化,关键是找出那些生命期嵌套在引用同一人接口的指针生命期内的接口指针。但这不是一件容易的事情。但有些情况还是比较明显的。如果函数的情况。如下,函数foo的生命包含在pIX的生命期中。 Void fool(IX* pIX2) { pIX2->Fx(); } IUnknown* pIUnknown = CreateInstance(); IX* pIX = NULL; HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)UpIX); pIUnknown->Release(); if (SUCCEEDED(hr)) { Foo(pIX); pIX->Release(); } 下面给出一些引用计数的规则 规则客户必须对每一个接口具有一个单独的引用计数值那样来处理各接口。因此,客户必须对不同的接口分别进行引用计数,即使它们的生命期是嵌套的。 一、输出参数规则 输出参数指的是给函数的调用者传回一个值的函数参数。从这一点上讲,输出参数的作用同函数的返回值是类似的。任何在输出参数中或作为返回值返回一个新的接口指针的函数必须对些接口指针调用AddRer 二、输入参数规则 对传入函数的接口指针,无需调用AddRef和Release,这是因为函数的生命期嵌套在调用者的生命期内。(见前面的例子) 三、输入-输出参数规则 输入-输出参数同时具有输入参数及输出参数的功能。在函数休中可以使用输入-输出参数的值,然后可以对这些值进行修改并将其返回给调用者。 在函数中,对于用输入-输出参数传递进来的接口指针,必须在给它赋另外一个接口指针值之前调用其Release。在函数返回之前,还必须对输出参数中所保存的接口指针调用AddRef.如下例所示。 Void ExchangeforCachedPtr(int i, IX **ppIX) { (*ppIX)->Fx(); // Do something with in-parameter. (*ppIX)->Release(); // Release in parameter. *ppIX = g_Cache[i]; // Get cached pointer (*ppIX)->AddRef(); // AddRef pointer. (*ppIX)->Fx(); // Do something with out-parameter. } 四、局部变量规则 对于局部自制的接口指针,由于它们只是在函数的生命其内才存在,因此无需调用AddRef和Release。这条规则实际是输入参数规则的直接结果。在下面的例子中,pIX2只是在函数foo的生命期内都在,因此可以保证其生命期将嵌套在所传入的pIX指针的生命期,因此无需对pIX2调用AddRef和Release。 Void foo(IX *pIX) { IX *pIX2 = pIX; pIX2->Fx(); } 五、全局变量规则 对于保存在全局变量中的接口指针,在将其传递给另外一个函数之前,必须调用其AddRef。由于此变量是全局性的,因此任何函数都可以通过调用其Release来终止其生命期。对于保存在成员变量中的接口指针,也应按此种方式进行处理。因为类中的任何成员函数都可以改变此种接口指针的状态。 六、不能确定时的规则 对于任何不定的情形,都应调用AddRef和Release对。 另外,在决定要进行优化时,应给那些没有进行引用计数的指针加上相应的注释,否则其它程序员在修改代码时,将可能会增大接口指针的生命期,从而合引用计数的优化遭到破坏。 忘记调用Release造成的错误可能比不调用AddRef造成的错误更难检测。 完整例子: #include <iostream> using namespacestd; #include <objbase.h> voidtrace(const char*msg) { cout <<msg <<endl; } //Forward references for GUIDs extern constIID IID_IX; extern constIID IID_IY; extern constIID IID_IZ; //Interfaces interfaceIX : IUnknown { virtual void__stdcall Fx() = 0; } ; interfaceIY : IUnknown { virtual void__stdcall Fy() = 0; } ; interfaceIZ : IUnknown { virtual void__stdcall Fz() = 0; } ; // //Component // classCA : publicIX, publicIY { //IUnknown implementation virtualHRESULT __stdcall QueryInterface(constIID&iid, void**ppv); virtualULONG __stdcall AddRef(); virtualULONG __stdcall Release(); //Interface IX implementation virtual void__stdcall Fx() { cout << "Fx" <<endl;} //Interface IY implementation virtual void__stdcall Fy() { cout << "Fy" <<endl;} public: //Constructor CA() : m_cRef(0) {} //Destructor ~CA() { trace("CA: Destroy self.");} private: longm_cRef; } ; HRESULT __stdcall CA::QueryInterface(constIID&iid, void**ppv) { if(iid ==IID_IUnknown) { trace("CA QI: Return pointer to IUnknown."); *ppv =static_cast<IX*>(this); } else if(iid ==IID_IX) { trace("CA QI: Return pointer to IX."); *ppv =static_cast<IX*>(this); } else if(iid ==IID_IY) { trace("CA QI: Return pointer to IY."); *ppv =static_cast<IY*>(this); } else { trace("CA QI: Interface not supported."); *ppv =NULL; returnE_NOINTERFACE; } reinterpret_cast<IUnknown*>(*ppv)->AddRef(); returnS_OK; } ULONG __stdcall CA::AddRef() { cout << "CA: AddRef = " <<m_cRef+1 << '.' <<endl; returnInterlockedIncrement(&m_cRef); } ULONG __stdcall CA::Release() { cout << "CA: Release = " <<m_cRef-1 << '.' <<endl; if(InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } returnm_cRef; } // //Creation function // IUnknown*CreateInstance() { IUnknown*pI =static_cast<IX*>(newCA); pI->AddRef(); returnpI; } // //IIDs // //{32bb8320-b41b-11cf-a6bb-0080c7b2d682} static constIID IID_IX = {0x32bb8320, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; //{32bb8321-b41b-11cf-a6bb-0080c7b2d682} static constIID IID_IY = {0x32bb8321, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; //{32bb8322-b41b-11cf-a6bb-0080c7b2d682} static constIID IID_IZ = {0x32bb8322, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; // //Client // intmain() { HRESULT hr; trace("Client: Get an IUnknown pointer."); IUnknown*pIUnknown =CreateInstance(); trace("Client: Get interface IX."); IX*pIX =NULL; hr =pIUnknown->QueryInterface(IID_IX, (void**)&pIX); if(SUCCEEDED(hr)) { trace("Client: Succeeded getting IX."); pIX->Fx(); //Use interface IX. pIX->Release(); } trace("Client: Get interface IY."); IY*pIY =NULL; hr =pIUnknown->QueryInterface(IID_IY, (void**)&pIY); if(SUCCEEDED(hr)) { trace("Client: Succeeded getting IY."); pIY->Fy(); //Use interface IY. pIY->Release(); } trace("Client: Ask for an unsupported interface."); IZ*pIZ =NULL; hr =pIUnknown->QueryInterface(IID_IZ, (void**)&pIZ); if(SUCCEEDED(hr)) { trace("Client: Succeeded in getting interface IZ."); pIZ->Fz(); pIZ->Release(); } else { trace("Client: Could not get interface IZ."); } trace("Client: Release IUnknown interface.") ; pIUnknown->Release() ; return 0; } |
随便看 |
百科全书收录4421916条中文百科知识,基本涵盖了大多数领域的百科知识,是一部内容开放、自由的电子版百科全书。