Why ATL Uses Template Classes

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

This article discusses why ATL uses template classes. This is not a discussion of ATL features, but just the principle behind the Template library.

Let us start with an implementation of the IUnknown interface. We all know that this is an interface that has to be supported by all COM objects. So, I thought of implementing this interface, thus making it convenient for everyone writing COM classes to extend it and be happy about IUnknown implementation. My objective is to implement IUnknown once and use it in all the COM classes I write in the future, thus reducing the burden of implementing it every time I write a COM class.

Let us try a method where we implement IUnknown in a class called IUnknownImpl and extend it to derive the IUnknown functionality in other classes.

Here is the C++ definition of the IUnknown interface:

Class IUnknown
{
public:
  virtual HRESULT QueryInterface(REFIID refiid, void **ppv) = 0;
  virtual ULONG    AddRef() = 0;
  virtual ULONG    Release() = 0;
};

The simplest possible implementation of IUnknown interface is as follows:

Class IUnknownImpl : public IUnknown
{
  ULONG m_cRef;
  REFIID  m_IID;

  public:

  IUnknownImpl(REFIID iid) : m_cRef(0), m_IID(iid)
  {
  }

  virtual HRESULT QueryInterface(REFIID refiid, void **ppv)
  {
    if(refiid  == IID_IUnknown || refiid == m_IID)
      *ppv = this;
    //I can not typecast this interface since
    //I don't have an interface to which I need to typecast
    else
      return E_NOINTERFACE;

    static_cast<IUnknown*>(this)->AddRef();

    return S_OK;
  }

  virtual ULONG AddRef()
  {
  return InterlockedIncrement(m_cRef);
  }

  virtual ULONG Release()
  {
    ULONG ul = 0;
    if(0 == (ul = InterlockedDecrement(m_cRef)))
    delete this;

    return ul;
  }
};

Let us define a COM interface called IMyInterface. This interface must extend IUnknown for obvious reasons.

Class IMyInterface : public IUnknown
{
  public:
  virtual HRESULT MyFunction(int iCount) = 0;
};

Because I already have IUnknown implementation, I need not implement IUnknown methods again. I will derive it from the IUnknownImpl class.

Class MyInterfaceImpl : public IMyInterface, IUnknownImpl
{
  public:
    MyInterfaceImpl() : IUnknownImpl(IID_IMyInterface)
    {
    }

    virtual HRESULT MyFunction(int iCount)
    {
    //do something
    return S_OK;
    }
};

Thus we implemented IMyInterface. But this implementation has a problem, visible readily to the minds of seasoned C++ developers. It has the grave diamond problem, as shown in the following illustration.

Let us try to modify IUnknownImpl class so that we avoid this problem. This implementation, instead of extending IUnknown, will implement IUnknown methods of any interface derived from IUnknown.

Template <class BASE_INTERFACE, REFIID iid >
    //BASE_INTERFACE should derive from IUnknown
Class IUnknownImpl2 : public BASE_INTERFACE
{
  ULONG m_cref ;

  public:
  IUnknownImpl(): m_cRef(0) {}

  virtual HRESULT QueryInterface(REFIID refiid, void **ppv)
  {
  if(refiid  == IID_IUnknown || refiid == iid)
    *ppv = static_cast<BASE_INTERFACE*>(this);
    // I have to typecast it to BASE_INTERFACE*, I know that
  else
    return E_NOINTERFACE;

        static_cast<IUnknown*>(this)->AddRef();

        return S_OK;
  }

  virtual ULONG AddRef()
  {
      return InterlockedIncrement(m_cRef);
  }

  virtual ULONG Release()
  {
    	   ULONG ul = 0;
  if(0 == (ul = InterlockedDecrement(m_cRef)))
    delete this;

         return ul;
  }
};

Now I can derive my class MyInterfaceImpl2 from IUnknownImpl2 in this way.

Class MyInterfaceImpl2 : IUnknownImpl2 < IMyInterface,
                                            IID_MYInterface>
{
// my methods
};

The inheritance pattern is now changed to:

The active template library is intended to take care of the COM basic functionalities like Object lifetime and Threading model management, implementation of standard interfaces IUnknown, IDispatch, IClassFactory, and so forth. Using this simple Template library, we can concentrate on implementing our component’s core functionality and forget about the other issues mentioned above.

Templates help us to write generic parameterized classes. As we saw in the above examples, with template-based implementation, we overcame the diamond problem while deriving from an implementation; we could typecast the “this” pointer to the required interface wherever it was required. For further discussion of ATL internals, I would recommend “ATL Internals” by Brent Rector and Chris Sells.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read