Implementing Interfaces: Multiple Inheritance

There are two different strategies for implementing interfaces on an object: multiple inheritance and interface containment. Which method works best for you depends first of all on your language of choice (languages that don't have an inheritance notion cannot support multiple inheritance, obviously) but if you are implementing an object in C++, which is a common occurrence, your choice depends on the object design itself.

Multiple inheritance works best for most objects. Declaring an object in this manner might appear as follows:


class CTextRender : public IDataObject, public IPersistFile {
   private:
      ULONG      m_cRef;         //Reference Count
      char *      m_pszText;      //Pointer to allocated text
      ULONG      m_cchText;      //Number of characters in m_pszText

      //Other internal member functions here

   public:
      [Constructor, Destructor]

      /*
       * We must override all interface member functions we
       * inherit to create an instantiatable class.
       */

      //IUnknown members shared between IDataObject and IPersistFile
      HRESULT QueryInterface(REFIID iid, void ** ppv);
      ULONG AddRef(void);
      ULONG Release(void);

      //IDataObject Members overrides
      HRESULT GetData(FORAMTETC *pFE, STGMEDIUM *pSTM);
      [Other members]
      ...

      //IPersistFile Member overrides
      HRESULT Load(char * pszFile, DWORD grfMode);
      [Other members]
      ...
   };

This object class inherits from the interfaces it wishes to implement, declares whatever variables are necessary for maintaining the object state, and overrides all the member functions of all inherited interfaces, remembering to include the IUnknown members that are present in all other interfaces. The implementation of the single QueryInterface function of this object would use typecasts to return pointers to different vtbl pointers:


HRESULT CTextRender::QueryInterface(REFIID iid, void ** ppv) {
   *ppv=NULL;

   //This code assumes an overloaded == operator for GUIDs exists
   if (IID_IUnknown==iid)
      *ppv=(void *)(IUnknown *)this;

   if (IID_IPersitFile==iid)
      *ppv=(void *)(IPersistFile *)this;

   if (IID_IDataObject==iid)
      *ppv=(void *)(IDataObject *)this;
   
   if (NULL==*ppv)
      return E_NOINTERFACE;      //iid not supported.
   
   // Any call to anyone's AddRef is our own, so we can just call that directly
   AddRef();
   return NOERROR;
   }

This technique has the advantage that all the implementation of all interfaces is gathered together in the same object and all functions have quick and direct access to all the other members of this object. In addition, there only needs to be one implementation of the IUnknown members. However, when we deal with aggregation in Chapter 6 we will see how an object might need a separate implementation of IUnknown by itself.