We can now look at those parts of the Animal object that specifically concern aggregation: creation and IUnknown implementation. Even if you were using multiple inheritance as an implementation technique, you must still have a separate implementation of IUnknown functions. For Animal, we'll have its object class CAnimal inherit only from IUnknown, the implementation of which controls the object as a whole. Animal then manages an interface implementation class, CImpIAnimal, for the IAnimal interface. Notice that you could also have CAnimal inherit from IAnimal and make a CImpIUnknown if you wanted. This would work great if you were using multiple inheritance for multiple interfaces on CAnimal. Either way you have two classes: one that delegates its IUnknown calls and one that controls the object as a whole. In the sample, CAnimal implements IUnknown, and CImpIAnimal_A implements IAnimal. Both classes have a pointer, m_pUnkOuter, with which to store the outer unknown.
This arrangement allows you to play a rather elegant trick on any delegating IUnknown functions. In the Object1 and Object2 samples described earlier, the interface implementations held a backpointer to the main object for the purpose of both IUnknown delegation and access to any object members as necessary. (The samples didn't show this, but that's what backpointers are for.) When creating an aggregatable object, you can separate these two roles of the backpointer and instead pass each interface implementation a backpointer and some IUnknown pointer to which that interface must delegate all of its calls. The trick is this: if the object is not being used in aggregation, the object passes its own IUnknown to the interface implementations. In aggregation, the object instead passes the outer unknown it receives at creation time. As a result, the interface implementations are oblivious to aggregation concerns: they blindly delegate to someone else's IUnknown, which can be easily switched as needed.
We can now see how the Animal object uses the outer unknown passed to its creation function. If the pointer is NULL, the aggregation is not happening, and it passes its own IUnknown to its interface implementation and allows the caller to request any interface pointer. If the pointer is non-NULL, it passes its IUnknown on to the interface and checks to be sure that the caller is asking for Animal's IUnknown in return:
HRESULT CreateAnimal(IUnknown *pUnkOuter, REFIID riid, void **ppv)
{
CAnimal *pObj;
if (NULL!=pUnkOuter && riid!=IID_IUnknown)
return ResultFromScode(CLASS_E_NOAGGREGATION);
pObj=new CAnimal(pUnkOuter);
if (NULL==pObj)
return FALSE;
if (!pObj->Init())
return FALSE;
return pObj->QueryInterface(riid, (PPVOID)ppv);
}
BOOL CAnimal::Init(void)
{
IUnknown *pUnkOuter=m_pUnkOuter;
//Set up the right unknown for delegation.
if (NULL==pUnkOuter)
pUnkOuter=this;
m_pImpIAnimal=new CImpIAnimal_A(this, pUnkOuter);
if (NULL==m_pImpIAnimal)
return FALSE;
return TRUE;
}
We can also see how CImpIAnimal simply stores whatever unknown it is given (no AddRef because the interface is always nested) so that it can delegate its IUnknown calls appropriately:
CImpIAnimal_A::CImpIAnimal_A(PCAnimal pObj, IUnknown *pUnkOuter)
{
m_cRef=0;
m_pObj=pObj;
m_pUnkOuter=pUnkOuter; //No AddRef; we're nested.
return;
}
Supporting aggregation is not a tremendous amount of work, for both outer and inner objects, but it must be consciously supported. Remember that if the Animal object itself created additional objects, it would pass the pUnkOuter argument it receives on creation down to the creation functions it calls, so KoalaA's pointer is propagated through the entire aggregation.