집계

집계는 외부 개체가 외부 개체 자체에서 구현된 것처럼 내부 개체의 인터페이스를 노출하는 개체 재사용 메커니즘입니다. 외부 개체가 해당 인터페이스 중 하나에 대한 모든 호출을 내부 개체의 동일한 인터페이스에 위임하는 경우에 유용합니다. 이 경우 외부 개체에서 추가 구현 오버헤드를 방지하기 위해 집계를 편리하게 사용할 수 있습니다. 집계는 실제로 특수한 포함/위임 사례입니다.

집계는 QueryInterface, AddRefRelease의 세 가지 IUnknown 함수를 제외하고 포함처럼 구현하는 것이 거의 간단합니다. catch는 클라이언트의 관점에서 외부 개체의 모든 IUnknown 함수가 외부 개체에 영향을 주어야 한다는 것입니다. 즉, AddRefRelease 는 외부 개체에 영향을 줍니다. QueryInterface 는 외부 개체에서 사용할 수 있는 모든 인터페이스를 노출합니다. 그러나 외부 개체가 단순히 내부 개체의 인터페이스를 자체적으로 노출하는 경우 해당 인터페이스를 통해 호출된 내부 개체의 IUnknown 멤버는 외부 개체의 인터페이스에 있는 IUnknown 멤버와 다르게 동작하며 IUnknown을 제어하는 규칙 및 속성을 절대 위반합니다.

해결 방법은 집계를 위해서는 내부 개체에서 IUnknown 을 명시적으로 구현하고 다른 인터페이스의 IUnknown 메서드를 외부 개체의 IUnknown 메서드에 위임해야 한다는 것입니다.

집계 가능한 개체 만들기

집계할 수 있는 개체를 만드는 것은 선택 사항입니다. 그러나 간단하게 수행할 수 있으며 상당한 이점을 제공합니다. 집계 가능한 개체를 만드는 데 적용되는 규칙은 다음과 같습니다.

  • 집계 가능(또는 내부) 개체의 IUnknown 인터페이스에 대한 QueryInterface, AddRefRelease 구현은 내부 개체의 참조 수를 제어하며, 이 구현은 외부 개체의 알 수 없는(제어하는 IUnknown)에 위임해서는 안 됩니다.
  • 다른 인터페이스에 대한 QueryInterface, AddRefRelease 의 집계 가능(또는 내부) 개체 구현은 제어 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 메서드를 호출한 다음, 내부 개체의 포인터에서 Release 를 호출합니다.

    // Obtaining inner object interface pointer 
    pUnkInner->QueryInterface(IID_ISomeInterface, &pISomeInterface); 
    pUnkOuter->Release(); 
    
    // Releasing inner object interface pointer 
    pUnkOuter->AddRef(); 
    pISomeInterface->Release(); 
    
    
  • 외부 개체는 인식할 수 없는 인터페이스에 대한 쿼리를 내부 개체에 맹목적으로 위임해서는 안 됩니다( 해당 동작이 특히 외부 개체의 의도인 경우 제외).

포함/위임