This article originally appeared in the September 1995 issue of the C++ Report. I have made it available here due to the widespread interest in COM smart pointers. An arguably more important follow-up piece, COM Smart Pointers Even More Harmful appeared in the February 1996 issue.

Interface Pointers Considered Harmful

Don Box

The Component Object Model (COM) is based upon the idea that the client of an object can only access the object via interface pointers that the object exports via QueryInterface. In the C++ language mapping, COM defines interfaces as abstract base classes with no data members, forcing the layout of all interfaces to be uniformly a pointer (vptr) to an array of function pointers (vtbl). To completely eliminate the client's need for object layout knowledge, object instantiation is performed by class factories, and objects are deleted implicitly based on reference counting. All interfaces provide a Release member function, and when an object receives the correct number of Release calls, it deletes itself. Ignoring the merits of using a reference count to control object lifetime, nothing stated above is a barrier to using COM with C++, in fact, COM and C++ generally mix well. However, problems can occur when implementing COM objects and clients if sufficient care is not used. This column focuses on client-side errors that can occur due to the "harmful" aspects of interface pointers.

Breaking the Rules

It is often said that COM is simply a collection of programming idioms that are used to implement C++ classes. To a certain extent this is true. COM is based largely on programming conventions and rules, and contains many more interface member functions than global API calls. Many of the rules in COM focus on the use of interface pointers and unfortunately, there is little language-level enforcement of these rules. Consider the definition of an interface, in this case IUnknown:
class IUnknown {
public:
  virtual HRESULT QueryInterface(const IID& riid,
                                 void **ppv) = 0;
  virtual unsigned long AddRef() = 0;
  virtual unsigned long Release() = 0;
};
In terms of normal C++ practices, something extremely important is missing here. To discover the problem, one first needs to look at an object implementation:
class CoString : public IUnknown {
  friend class CoStringClassFactory;
  char *m_text;

  CoString()
  : m_text(new char[256])  {
  }

  virtual ~CoString()  {
    delete[] m_text;
  }
// IUnknown implementation deleted for clarity
};
and some innocent looking client code that accesses the object via its interface:
void Make2Strings(IUnknown * &punk1, IUnknown * &punk2){
  punk1 = punk2 = 0;
// create an instance of CoString
  HRESULT hr1 = CoCreateInstance(CLSID_CoString,
                                 0,
                                 CLSCTX_ALL,
                                 IID_IUnknown,
                                 (void**)&punk1);
  if (FAILED(hr1))
    return;

// create another instance of CoString
  HRESULT hr2 = CoCreateInstance(CLSID_CoString,
                                 0,
                                 CLSCTX_ALL,
                                 IID_IUnknown,
                                 (void**)&punk2);

// free 1st instance if second instantiate fails
  if (FAILED(hr2) && SUCCEEDED(hr1)) {
    delete punk1; // Hazardous line #1
    punk1 = punk2 = 0;
  }
}
The code above will compile and link without even a warning. However, if the second allocation fails, the client's (and potentially the object's) heap will be corrupt after executing this function. This is not the desired effect and is an artifact of the client (in this case the function Make2Strings()) not following the first rule of interface pointers:
Never apply the delete operator to an interface pointer.
This rule is required for two reasons. The most obvious reason is that the object was potentially implemented using a different compiler than the client, and the behavior of mixing new and delete from different environments is pretty well defined (at least under Windows): it doesn't work. The less obvious reason for restricting the use of delete applies to both the client and object implementation code, and it is based on the behavior of the delete operator and destructors.

Consider the following class factory implementation:

HRESULT 
CoStringClassFactory::CreateInstance(IUnknown *pUnkOuter,
                                     REFIID riid,
                                     void **ppv)
{
  *ppv = 0; // default to null/failure
  if (pUnkOuter != 0) 
    return CLASS_E_NOAGGREGATION;
// create a new CoString (this function is compiled by
// the same environment as CoString, so new/delete are 
// legal).
  IUnknown *p = new CoString;
  if (p == 0)
    return E_OUTOFMEMORY;
  if (FAILED(p->QueryInterface(riid, ppv))) {
    delete p; // Hazardous line #2
    return E_NOINTERFACE;
  }
  return S_OK;
}
This code will leak 256 bytes if the call to QueryInterface fails, as IUnknown does not have a virtual destructor (since IUnknown * was never meant to be a valid argument to delete). To correct this defect, the code above needs to either (a) call p->Release() or (b) change the type of p from IUnknown * to CoString *.

These errors could have been prevented at compile-time if Microsoft had defined a virtual destructor in IUnknown, but in doing so, it would be sending the misleading message to developers that calling delete on an interface pointer is something one might actually want to do. This also would have changed the VTBL layout of all interfaces, as an entry would be needed for the destructor. More problematically, this would require that there be some implementation of ~IUnknown for derived implementation classes to implicitly call. Even if there was a way to provide such an implementation in a compiler-independent binary form, it would place a greater burden on non-C++ based object implementations.

Defining interfaces with virtual destructors would have clearly been the wrong approach. A more reasonable solution would have been for IUnknown to provide a protected, non-virtual destructor with an empty implementation, as follows:

class IUnknown {
public:
  virtual HRESULT QueryInterface(const IID& riid,
                                 void **ppv) = 0;
  virtual unsigned long AddRef() = 0;
  virtual unsigned long Release() = 0;
protected:
  ~IUnknown() {}
};
This achieves the desired semantic effect precisely. Clients cannot apply delete to interface pointers. Object implementations cannot incorrectly delete through base interface pointers, bypassing the derived destructors. Object implementations can only delete objects through non-interface pointers.

Surprisingly, this is not the approach Microsoft chose. It was only after implementing and examining an interface hierarchy using both approaches (no destructor or protected non-virtual inline destructor as above) that I was able to understand one possible reason why. The generated code was considerably larger in the protected destructor case (over 20 machine instructions per interface per destructor). Much of this overhead is due to the exception-handling code that must be inserted by the compiler to ensure correct behavior in the case of constructor failure. Given that most COM classes implement between 4-10 interfaces, each of which derives from at least one other interface (often IUnknown), this would result in hundreds of additional machine instructions per destructor invocation that perform no useful function except to enforce a rule at compile-time. Given the tight resource constraints that are present under many Windows installations, the decision to not include protected destructors was not without justification. [Note: After writing this article, I thought up a simpler and workable solution to this problem. I published this solution in the companion piece, COM Smart Pointers Also Considered Harmful]

Exceptional Errors

Another class of errors that occur when using interface pointers is related to C++ exception handling. Consider the following:
void f(IUnknown *);
void g() {
  IUnknown *punk = 0;
// create an instance of CoString
  if (SUCCEEDED(CoCreateInstance(CLSID_CoString,
                                 0,
                                 CLSCTX_ALL,
                                 IID_IUnknown,
                                 (void**)&punk)))
  {
    f(punk); 
    punk->Release(); 
  }
}
Again, this code looks completely innocuous. If, however, the implementation of f() looks like this:
void f(IUnknown *) {
  throw "Some Exception";
}
then a call to g() is guaranteed to leak a CoString object. To ensure that the Release call is made in the face of exceptions, an auto-release class is needed:
class AutoRelease {
  IUnknown *m_punk;
// prevent shallow copy
  const AutoRelease & operator =(const AutoRelease &);
  AutoRelease(const AutoRelease &);
public:
  AutoRelease(IUnknown *punk)
  : m_punk(punk) {
  }

  ~AutoRelease() {
    if (m_punk) 
      m_punk->Release();
  }
};
Given the class above, g() can be made exception-safe as follows:
void g() {
  IUnknown *punk = 0;
// create an instance of CoString
  if (SUCCEEDED(CoCreateInstance(CLSID_CoString,
                                 0,
                                 CLSCTX_ALL,
                                 IID_IUnknown,
                                 (void**)&punk)))
  {
    AutoRelease ar_punk(punk);
    f(); 
  }
}
While this code is now more robust, it is also more difficult to maintain. Since the AutoRelease class provides no utility beyond ensuring that Release is called, it is relatively easy to neglect its use. Additionally, it is still possible to explicitly call Release on the interface pointer directly, resulting in certain disaster.

Type Errors

Despite the heavy reliance on the type system in COM, one of the most common and easily made errors is due to a gaping hole in the type system imposed by COM. QueryInterface, the ubiquitous member function that is used to ask an object to coerce an interface pointer from one type to another a la dynamic_cast, has a (necessarily) error-prone signature:
HRESULT QueryInterface(const IID& riid, void** ppv);
Clients call QueryInterface providing a pointer to an interface pointer as the second parameter together with a GUID, that identifies the type of interface pointer that is expected:
ISomeInterface *psi;
punk->QueryInterface(IID_ISomeInterface, // describes arg2
                     (void**)&psi));     // arg2
Unfortunately, the following looks equally correct to the compiler:
ISomeInterface *psi;
punk->QueryInterface(IID_IOther,       // bad desc. of psi
                     (void**)&psi));   

This more subtle variation also compiles correctly:
ISomeInterface *psi;
punk->QueryInterface(IID_ISomeInterface, 
                     (void**)psi));      // too direct...
Given that the rules of inheritance do not apply to pointers, this alternative definition of QueryInterface would not have alleviated the problem:
HRESULT QueryInterface(const IID& riid, IUnknown** ppv);
as implicit upcasting only applies to pointers, not pointers to pointers:
IDerived **ppd;
IBase **ppb = ppd; // illegal
This same limitation applies to references to pointers as well. The following alternative definition would have been arguably more convenient from the client side:
HRESULT QueryInterface(const IID& riid, void* ppv);
as it would have allowed clients to forgo the cast. Unfortunately, this solution would not reduce the number of errors (both errors above would still be possible) and, by eliminating the need for a cast, would remove a visual indicator that type safety might be in jeopardy. Given the desired semantics of QueryInterface, the argument types Microsoft chose are reasonable, if not type-safe or elegant.

Breaking More Rules

COM requires that clients respect the reference count of an object when assigning interface pointers to one another, as well as when the lifetime of an interface pointer has ended. The following code demonstrates normal client practices:
void f(IUnknown *punkArg1, IUnknown *punkArg2)
{
  IUnknown *punkTemp = 0;

// assign punkTemp to 1st object
  punkTemp = punkArg1;   // perform assignment
  punkTemp->AddRef();    // adjust reference count

// assign punkTemp to 2nd object
  punkTemp->Release();   // destroy current value
  punkTemp = punkArg1;   // perform assignment
  punkTemp->AddRef();    // adjust reference count

// fall out of scope
  punkTemp->Release();   // destroy current value
}
For a simple function like the one above, it is legal to hand-optimize away all calls to AddRef and Release, however, it is not always practical to do so in production code that is more complex than the code shown here. Additionally, such optimizations often do not yield as high a return as one might anticipate, as AddRef/Release calls usually do not need to cross process boundaries (the in-process proxy maintains its own reference count, essentially buffering redundant references).

Reducing Risk

One technique that can greatly reduce the risks associated with COM interface pointers is to hide them behind a smart pointer class, eliminating the need to make raw IUnknown calls. Ideally, a COM smart interface pointer would: Implementing a smart pointer to satisfy the requirements above is relatively straight forward, as is shown in this minimal smart pointer class:
template <class Interface, const IID* piid>
class SmartInterface
{
  Interface *m_pif;
public:
  class XBadPtrException { };
// "native" copy ctors only addref
  SmartInterface(Interface *pif = 0) : m_pif(pif) {
    if (m_pif) m_pif->AddRef();
  }

  SmartInterface(const SmartInterface& 
                            rhs) 
  : m_pif(rhs.m_pif) {
    if (m_pif) m_pif->AddRef();
  }

// "foreign" copy ctor needs to QueryInterface
  SmartInterface(IUnknown *pif) : m_pif(0) {
    if (pif) pif->QueryInterface(*piid, (void**)&m_pif);
  }

// destructor auto-releases
  ~SmartInterface() {
    if (m_pif) m_pif->Release();
  }

// "native" assignment only addrefs after releasing
  SmartInterface& operator = (Interface *pif) {
    if (m_pif != pif) {
      if (m_pif) m_pif->Release();
      m_pif = pif;
      if (m_pif) m_pif->AddRef();
    }
    return *this;
  }
  
  SmartInterface& 
  operator = (const SmartInterface & rhs){
    return operator = (rhs.m_pif);
  }

// "foreign" assignment must QueryInterface after releasing
  SmartInterface& operator = (IUnknown *pif) {
    if (m_pif != pif) {
      if (m_pif) m_pif->Release();
      if (pif) 
        pif->QueryInterface(*piid, (void**)&m_pif);
      else
        m_pif = 0;
    }
    return *this;
  }

// standard smart-pointer operators

  Interface * operator -> () {
    if (m_pif == 0) throw XBadPtrException();
    return m_pif;
  }

  operator Interface * () {
    return m_pif;
  }

  Interface ** operator & () {
    return &m_pif;
  }

  BOOL operator ! () const {
    return m_pif == 0;
  }

  void * * ppv() {
    return (void**)&m_pif;
  }

  const IID& GetIID() const  {
    return *piid;
  }
};

// define macro for QueryInterface-style arguments
#define IID_PPV(SmartIf) (SmartIf).GetIID(),(SmartIf).ppv()
// define macro to token paste template args
#define SMART_INTERFACE(If) SmartInterface
Given the class above, the following non-smart pointer code
void LoadAndDraw(LPCOLESTR szFileName, HDC hdc, RECTL& r) {
  IStorage *pstg = 0;
  IPersistStorage *pps = 0;
  IViewObject *pvo = 0 ;

  StgOpenStorage(szFileName, ..., &pstg);
  if (pstg) {
    OleLoad(pstg, IID_IPeristStorage, 0, (void**)&pps);
    if (pps) {
      pps->QueryInterface(IID_IViewObject, (void**)&pvo);
      if (pvo) {
        pvo->Draw(..., hdc, &r, ...);
        pvo->Release();
      }
      pps->Release();
    }
    pstg->Release();
  }
}
can be greatly simplified through the use of smart pointers:
void LoadAndDraw(LPCOLESTR szFileName, HDC hdc, RECTL& r) {
  SmartInterface<IStorage, &IID_IStorage> pstg;
  SmartInterface<IPersistStorage,&IID_IPersistStorage> pps;
  SmartInterface<IViewObject, &IID_IViewObject> pvo;

  StgOpenStorage(szFileName, ..., &pstg);
  if (!pstg) 
    return;
  OleLoad(pstg, pps.GetIID(), 0, pps.ppv());
  if (pvo = pps)
    pvo->Draw(..., hdc, &r, ...);
}
The code above is able to leave the function from any point due to the behavior of the smart pointer destructor. It is common to see non-smart pointer-based code use gotos or highly nested if statements to ensure that all interface pointers are properly released.

One serious limitation of the template class shown above is that it is illegal to instantiate the template using IUnknown as the first template argument. This is due to the fact that using IUnknown produces two duplicate constructors and two duplicate assignment operators (one each for the template argument Interface, one each hard coded to IUnknown). To support IUnknown, a second, special-case class is needed that is a simplification of the SmartInterface class shown. The only modification required is the removal of the "foreign" constructor and assignment operator. A complete implementation of both classes is available by sending electronic mail to combind-request@develop.com. [Note: Please do not send requests to this email address. You can download a version of this smart pointer from http://www.develop.com/combind.htm]

Trailer

COM was designed to allow objects to be loaded dynamically irrespective of the compiler used to implement the object or the object's internal implementation. To this end, COM interface pointers perform more than adequately. However, as this column has illustrated, certain compromises were necessary to efficiently support this implementation-independence, and some of these compromises make implementing client code somewhat more error-prone. Using smart pointers can minimize the risks associated with interface pointers considerably. [Note: I had many insights after writing this article and wrote a follow up some months later. ]
Back to Don's Home Page