Aggregazione
L'aggregazione è il meccanismo di riutilizzo degli oggetti in cui l'oggetto esterno espone interfacce dall'oggetto interno come se fossero implementate sull'oggetto esterno stesso. Ciò è utile quando l'oggetto esterno delega ogni chiamata a una delle relative interfacce alla stessa interfaccia nell'oggetto interno. L'aggregazione è disponibile per praticità per evitare un sovraccarico di implementazione aggiuntivo nell'oggetto esterno in questo caso. L'aggregazione è in realtà un caso specializzato di contenimento/delega.
L'aggregazione è quasi semplice da implementare come contenimento, ad eccezione delle tre funzioni IUnknown: QueryInterface, AddRef e Release. Il catch è che dal punto di vista del client, qualsiasi funzione IUnknown sull'oggetto esterno deve influire sull'oggetto esterno. Ovvero, AddRef e Release influiscono sull'oggetto esterno e QueryInterface espone tutte le interfacce disponibili nell'oggetto esterno. Tuttavia, se l'oggetto esterno espone semplicemente l'interfaccia di un oggetto interno come propria, i membri IUnknown dell'oggetto interno chiamati tramite tale interfaccia si comportano in modo diverso rispetto ai membri IUnknown sulle interfacce dell'oggetto esterno, una violazione assoluta delle regole e delle proprietà che regolano IUnknown.
La soluzione consiste nel fatto che l'aggregazione richiede un'implementazione esplicita di IUnknown sull'oggetto interno e la delega dei metodi IUnknown di qualsiasi altra interfaccia ai metodi IUnknown dell'oggetto esterno.
La creazione di oggetti che possono essere aggregati è facoltativa; tuttavia, è semplice da fare e offre vantaggi significativi. Le regole seguenti si applicano alla creazione di un oggetto aggregabile:
- L'implementazione dell'oggetto aggregabile (o interno) di QueryInterface, AddRef e Release per l'interfaccia IUnknown controlla il conteggio dei riferimenti dell'oggetto interno e questa implementazione non deve delegare all'oggetto esterno sconosciuto (il controllo IUnknown).
- L'implementazione dell'oggetto aggregabile (o interno) di QueryInterface, AddRef e Release per le altre interfacce deve delegare al controllo IUnknown e non deve influire direttamente sul conteggio dei riferimenti dell'oggetto interno.
- L'oggetto IUnknown interno deve implementare QueryInterface solo per l'oggetto interno.
- L'oggetto aggregabile non deve chiamare AddRef quando si contiene un riferimento al puntatore IUnknown di controllo.
- Quando l'oggetto viene creato, se viene richiesta un'interfaccia diversa da IUnknown , la creazione deve avere esito negativo con E_NOINTERFACE.
Il frammento di codice seguente illustra un'implementazione corretta di un oggetto aggregabile usando il metodo della classe annidata di implementazione delle interfacce:
// 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;
};
};
Quando si sviluppa un oggetto aggregabile, si applicano le regole seguenti:
Quando si crea l'oggetto interno, l'oggetto esterno deve richiedere esplicitamente il relativo oggetto IUnknown.
L'oggetto esterno deve proteggere l'implementazione di Release dalla reentrancy con un conteggio dei riferimenti artificiali intorno al codice di distruzione.
L'oggetto esterno deve chiamare il relativo metodo IUnknownRelease di controllo se esegue una query per un puntatore a una qualsiasi delle interfacce dell'oggetto interno. Per liberare questo puntatore, l'oggetto esterno chiama il relativo metodo IUnknownAddRef, seguito da Release sul puntatore dell'oggetto interno.
// Obtaining inner object interface pointer pUnkInner->QueryInterface(IID_ISomeInterface, &pISomeInterface); pUnkOuter->Release(); // Releasing inner object interface pointer pUnkOuter->AddRef(); pISomeInterface->Release();
L'oggetto esterno non deve delegare in modo cieco una query per qualsiasi interfaccia non riconosciuta all'oggetto interno, a meno che tale comportamento non sia specificamente l'intenzione dell'oggetto esterno.