Search code examples
winapicomms-media-foundation

Why Win32 COM example has a public constructor and private destructor


I'm trying to understand this example of asynchronous callback:

https://learn.microsoft.com/en-us/windows/win32/medfound/using-the-source-reader-in-asynchronous-mode

The callback interface has a public constructor:

public:
  SourceReaderCB(HANDLE hEvent) : 
    m_nRefCount(1), m_hEvent(hEvent), m_bEOS(FALSE), m_hrStatus(S_OK)
  {
    InitializeCriticalSection(&m_critsec);
  }

But the destructor is private:

private:

  // Destructor is private. Caller should call Release.
  virtual ~SourceReaderCB() 
  {
  }

And the allocation is done using new in the example:

// Create an instance of the callback object.
pCallback = new (std::nothrow) SourceReaderCB(hEvent);
if (pCallback == NULL)
{
    hr = E_OUTOFMEMORY;
    goto done;
}

It looks like the object is a smart pointer internally, and a raw pointer on the outside. But if I never delete the pointer, won't it leak memory anyway?


Solution

  • There are two aspects here that are somewhat intermingled, making it difficult to keep them separated.

    First, there's COM: A binary, language-agnostic protocol that enables sharing objects across languages, modules, processes, and machines. It describes, among others, how methods are invoked on objects, and how object lifetimes are managed (see Managing the Lifetime of an Object). Lifetime management is exposed through the IUnknown interface, the base interface inherited by all COM interfaces. Clients call AddRef() whenever they need another copy of the interface pointer (e.g. when passing it elsewhere), and Release() when they are done with their copy of the interface pointer. This allows the COM object to track outstanding references, and free resources when the reference count drops to zero.

    This is where the actual implementation comes into play (the other aspect). Beyond the ABI contract, COM places no restrictions on the implementation. The referenced sample chooses C++, which makes the code both easier and harder:

    Microsoft's compiler models C++ inheritance in a way that's compatible with COM's vtable layout, meaning that you don't have to manually construct the function pointer tables (as you would have to when using C, for example). class SourceReaderCB : public IMFSourceReaderCallback arranges everything for you, so that you can pass a pointer to SourceReaderCB to clients that expect an IMFSourceReaderCallback interface pointer.

    On the other hand, COM's manual lifetime management doesn't align well with C++' automatic storage duration. This is why the C++ d'tor-trickery is needed to harden the implementation against misuse. For callbacks in particular, the COM object backing the implementation commonly needs to outlive the scope where it is created. Making the d'tor private prevents the following:

    • Creation of an object with automatic lifetime; this fails to compile as the compiler attempts to inject a d'tor-call at the end of the sope
    • Accidental calls to delete on the object; clients are required to go through the interface's Release() which will eventually clean up resources from within the implementation

    This explains why the d'tor is private, but doesn't address why the c'tor is public then. I suppose this is purely for convenience, reducing the sample code to not add too much distraction. Personally, I would have made the c'tor private, too, and instead implemented a public factory function, e.g.

    private:
        SourceReaderCB(HANDLE hEvent);
    
    public:
        static IMFSourceReader* Create(HANDLE hEvent) {
            return new SourceReaderCB(hEvent);
       }
    

    The benefit here is that both construction and destruction are now a private implementation detail, which can both be made to match, as well as change over time without affecting clients. Leaving the construction to consumers poses the risk, that they pick the wrong allocation scheme (e.g. malloc instead of new).