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.