Contained Interface Classes (CObject2)

One reason why interface implementations are rather verbose is that you end up defining a bunch of separate classes for each object. One way to combine the classes is to use contained or nested classes for each interface, as demonstrated in the CObject2 class in OBJECT2.H:


class CObject2 : public IUnknown
{
class CImpISampleOne : public ISampleOne
{
private:
DWORD m_cRef; //For debugging
CObject2 *m_pObj; //Backpointer for delegation

public:
CImpISampleOne(CObject2 *pObj)
{ m_cRef=0; m_pObj=pObj; }

~CImpISampleOne(void)
{ }

//IUnknown members
STDMETHODIMP QueryInterface(REFIID, PPVOID);
STDMETHODIMP_(DWORD) AddRef(void);
STDMETHODIMP_(DWORD) Release(void);

//ISampleOne members
STDMETHODIMP GetMessage(LPTSTR, UINT);
};

class CImpISampleTwo : public ISampleTwo
{
private:
DWORD m_cRef; //For debugging
CObject2 *m_pObj; //Backpointer for delegation

public:
CImpISampleTwo(CObject2 *pObj)
{ m_cRef=0; m_pObj=pObj; }
~CImpISampleTwo(void)
{ }

//IUnknown members
STDMETHODIMP QueryInterface(REFIID, PPVOID);
STDMETHODIMP_(DWORD) AddRef(void);
STDMETHODIMP_(DWORD) Release(void);

//ISampleTwo members
STDMETHODIMP GetString(LPTSTR, UINT);
};

friend CImpISampleOne;
friend CImpISampleTwo;

private:
DWORD m_cRef; //Object reference count

CImpISampleOne m_ImpISampleOne;
CImpISampleTwo m_ImpISampleTwo;

public:
CObject2(void);
~CObject2(void);

//IUnknown members
STDMETHODIMP QueryInterface(REFIID, PPVOID);
STDMETHODIMP_(DWORD) AddRef(void);
STDMETHODIMP_(DWORD) Release(void);
};

typedef CObject2 *PCObject2;

The only real difference here is that instead of the object managing pointers to each interface, it directly manages the implementation objects themselves. This means that constructing an instance of CObject2 will automatically instantiate the interface classes; destruction is automatic as well. We don't need any extra Init function to complete the construction of the object, but we do have to play tricks with the object constructor, as shown in the code at the top of the following page.


CObject2::CObject2(void)
: m_ImpISampleOne(this), m_ImpISampleTwo(this)
{
m_cRef=0;
return;
}

This is the only way I found to get the object's this pointer into the interface classes to serve as the backpointer.

The object's QueryInterface implementation looks much the same as in CObject1 except that we have to return the address of the contained objects instead of pointers:


STDMETHODIMP CObject2::QueryInterface(REFIID riid, PPVOID ppv)
{
*ppv=NULL;

//IUnknown comes from CObject2.
if (IID_IUnknown==riid)
*ppv=this;

//Other interfaces come from contained classes.
if (IID_ISampleOne==riid)
*ppv=&m_ImpISampleOne;

if (IID_ISampleTwo==riid)
*ppv=&m_ImpISampleTwo;

if (NULL==*ppv)
return ResultFromScode(E_NOINTERFACE);

((LPUNKNOWN)*ppv)->AddRef();
return NOERROR;
}

Other than that, the implementations of the contained classes are exactly the same (you'll notice that the constructor and destructor are declared in line in this sample), even the delegation of IUnknown calls to CObject2.

Contained classes are a little more concise than interface implementations, and the technique automates the creation of interface classes. This makes the creation process harder to trace, but that's not a big issue. I find the class declarations harder to read, which is why I don't use this technique in this book. You might find it beneficial in your own projects to limit the scope of an interface class to the object that uses it—especially if you have in the same source code multiple objects that implement the same interfaces but need those interface implementations to be different. This technique allows you to scope the interface class names within the object class name, whereas the interface implementations technique requires globally unique interface class names.