集合体

聚合是对象重用机制,其中外部对象从内部对象公开接口,就像在外部对象本身上实现接口一样。 当外部对象将每个对其接口的调用委托给内部对象中的同一接口时,这非常有用。 聚合是一种便利,可避免在外部对象中出现额外的实现开销。在这种情况下,聚合是一种便利。 聚合实际上是 包含/委派的专用案例。

聚合几乎与实现包含一样简单,除了三个 IUnknown 函数之外:QueryInterfaceAddRefRelease。 catch 是,从客户端的角度来看,外部对象上的任何 IUnknown 函数都必须影响外部对象。 也就是说,AddRefRelease 影响外部对象,QueryInterface 公开外部对象上可用的所有接口。 但是,如果外部对象只是将内部对象的接口公开为自己的接口,则通过该接口调用的内部对象的 IUnknown 成员的行为将不同于外部对象的接口上的 IUnknown 成员,这绝对违反了控制 IUnknown的规则和属性。

解决方案是,聚合需要在内部对象上显式实现 IUnknown,并将 IUnknown 方法的 IUnknown 方法显式实现到外部对象的 IUnknown 方法。

创建可聚合对象

创建可聚合的对象是可选的;但是,这样做很简单,并提供了显著的好处。 以下规则适用于创建可聚合对象:

  • 可聚合的(或内部)对象的 QueryInterfaceAddRef的可聚合(或内部)对象的实现,并且 释放 对其 IUnknown 接口控制内部对象的引用计数,并且此实现不得委托给外部对象的未知(控制 IUnknown)。
  • 可聚合的(或内部)对象的 QueryInterfaceAddRef的可聚合(或内部)对象的实现,并且 发布 的其他接口必须委托给控制 IUnknown,并且不能直接影响内部对象的引用计数。
  • 内部 IUnknown 必须仅为内部对象实现 QueryInterface
  • 当持有对控制 IUnknown 指针的引用时,聚合对象不得调用 AddRef
  • 创建对象时,如果请求 IUnknown 以外的任何接口,则创建必须失败并E_NOINTERFACE。

以下代码片段演示了使用实现接口的嵌套类方法实现可聚合对象的正确实现:

// CSomeObject is an aggregable object that implements 
// IUnknown and ISomeInterface 
class CSomeObject : public IUnknown 
{ 
    private: 
        DWORD        m_cRef;         // Object reference count 
        IUnknown*    m_pUnkOuter;    // Controlling IUnknown, no AddRef 
 
        // Nested class to implement the ISomeInterface interface 
        class CImpSomeInterface : public ISomeInterface 
        { 
            friend class CSomeObject ; 
            private: 
                DWORD    m_cRef;    // Interface ref-count, for debugging 
                IUnknown*    m_pUnkOuter;    // Controlling IUnknown 
            public: 
                CImpSomeInterface() { m_cRef = 0;   }; 
                ~ CImpSomeInterface(void) {}; 
 
                // IUnknown members delegate to the outer unknown 
                // IUnknown members do not control lifetime of object 
                STDMETHODIMP     QueryInterface(REFIID riid, void** ppv) 
                {    return m_pUnkOuter->QueryInterface(riid,ppv);   }; 
 
                STDMETHODIMP_(DWORD) AddRef(void) 
                {    return m_pUnkOuter->AddRef();   }; 
 
                STDMETHODIMP_(DWORD) Release(void) 
                {    return m_pUnkOuter->Release();   }; 
 
                // ISomeInterface members 
                STDMETHODIMP SomeMethod(void) 
                {    return S_OK;   }; 
        } ; 
        CImpSomeInterface m_ImpSomeInterface ; 
    public: 
        CSomeObject(IUnknown * pUnkOuter) 
        { 
            m_cRef=0; 
            // No AddRef necessary if non-NULL as we're aggregated. 
            m_pUnkOuter=pUnkOuter; 
            m_ImpSomeInterface.m_pUnkOuter=pUnkOuter; 
        } ; 
        ~CSomeObject(void) {} ; 
 
        // Static member function for creating new instances (don't use 
        // new directly). Protects against outer objects asking for 
        // interfaces other than Iunknown. 
        static HRESULT Create(IUnknown* pUnkOuter, REFIID riid, void **ppv) 
        { 
            CSomeObject*        pObj; 
            if (pUnkOuter != NULL && riid != IID_IUnknown) 
                return CLASS_E_NOAGGREGATION; 
            pObj = new CSomeObject(pUnkOuter); 
            if (pObj == NULL) 
                return E_OUTOFMEMORY; 
            // Set up the right unknown for delegation (the non-
            // aggregation case) 
            if (pUnkOuter == NULL) 
            {
                pObj->m_pUnkOuter = (IUnknown*)pObj ; 
                pObj->m_ImpSomeInterface.m_pUnkOuter = (IUnknown*)pObj;
            }
            HRESULT hr; 
            if (FAILED(hr = pObj->QueryInterface(riid, (void**)ppv))) 
                delete pObj ; 
            return hr; 
        } 
 
        // Inner IUnknown members, non-delegating 
        // Inner QueryInterface only controls inner object 
        STDMETHODIMP QueryInterface(REFIID riid, void** ppv) 
        { 
            *ppv=NULL; 
            if (riid == IID_IUnknown) 
                *ppv=this; 
            if (riid == IID_ISomeInterface) 
                *ppv=&m_ImpSomeInterface; 
            if (NULL==*ppv) 
                return ResultFromScode(E_NOINTERFACE); 
            ((IUnknown*)*ppv)->AddRef(); 
            return NOERROR; 
        } ; 
        STDMETHODIMP_(DWORD) AddRef(void) 
        {    return ++m_cRef; }; 
        STDMETHODIMP_(DWORD) Release(void) 
        { 
            if (--m_cRef != 0) 
                return m_cRef; 
            delete this; 
            return 0; 
        }; 
}; 
 

聚合对象

开发可聚合对象时,将应用以下规则:

  • 创建内部对象时,外部对象必须显式请求其 IUnknown

  • 外部对象必须保护其实现 Release,使其在销毁代码周围使用人工引用计数重新进入。

  • 如果外部对象查询指向任何内部对象的接口的指针,则外部对象必须调用其控制 IUnknownRelease 方法。 若要释放此指针,外部对象调用其控制 IUnknownAddRef 方法,然后 释放 内部对象的指针。

    // Obtaining inner object interface pointer 
    pUnkInner->QueryInterface(IID_ISomeInterface, &pISomeInterface); 
    pUnkOuter->Release(); 
    
    // Releasing inner object interface pointer 
    pUnkOuter->AddRef(); 
    pISomeInterface->Release(); 
    
    
  • 外部对象不得盲目地将任何无法识别的接口的查询委托给内部对象,除非该行为是外部对象的意图。

包含/委派