September 28th, 2005

Avoiding double-destruction when an object is released

As we saw last time, trying to do too much in one’s destructor can lead to an object being destroyed twice. The standard way to work around this problem is to set an artificial reference count during destruction.

class MyObject : public IUnknown
{
 ...
 ULONG Release()
 {
  LONG cRef = InterlockedDecrement(&m_cRef);
  if (cRef == 0) {
   m_cRef = DESTRUCTOR_REFCOUNT;
   delete this;
  }
  return cRef;
 }
 ...
private:
 }
 enum { DESTRUCTOR_REFCOUNT = 42 };
 ~MyObject()
 {
  if (m_fNeedSave) Save();
  assert(m_cRef == DESTRUCTOR_REFCOUNT);
 }
};

If you have a common implementation of IUnknown, you can set the reference count to DESTRUCTOR_REFCOUNT in your implementation of IUnknown::Release like we did here, and assert that the value is correct in your implementation’s destructor. Since C++ runs base class destructors after derived class destructors, your base class destructor will check the reference count after the derived class has done its cleanup.

By setting the reference count to an artificial non-zero value, any AddRef() and Release() calls that occur will not trigger a duplicate destruction (assuming of course that nobody in the destructor path has a bug that causes them to over-release). The assertion at the end ensures that no new references to the object have been created during destruction.

This is really more of a workaround than a rock-solid solution, because it assumes that no functions called during the destruction sequence retain a reference to the object beyond the function’s return. This is in general not something you can assume about COM. In general, a method is free to call AddRef and hang onto a pointer to an object in order to complete the requested operation later. Some methods (such as the IPersistPropertyBag::Load method) explicitly forbid such behavior, but these types of methods are more the exception rather than the rule.

Exercise: Why is it safe to perform a simple assignment m_cRef = DESTRUCTOR_REFCOUNT instead of the more complicated InterlockedExchangeAdd(&m_cRef, DESTRUCTOR_REFCOUNT)?

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.