February 2015
Volume 30 Number 2
Windows with C++ - COM Smart Pointers Revisited
By Kenny Kerr | February 2015
After the second coming of COM, otherwise known as the Windows Runtime, the need for an efficient and reliable smart pointer for COM interfaces is more important than ever. But what makes for a good COM smart pointer? The ATL CComPtr class template has been the de facto COM smart pointer for what feels like decades. The Windows SDK for Windows 8 introduced the ComPtr class template as part of the Windows Runtime C++ Template Library (WRL), which some hailed as a modern replacement for the ATL CComPtr. At first, I also thought this was a good step forward, but after a lot of experience using the WRL ComPtr, I’ve come to the conclusion it should be avoided. Why? Keep reading.
So what should be done? Should we return to ATL? By no means, but perhaps it’s time to apply some of the modern C++ offered by Visual C++ 2015 to the design of a new smart pointer for COM interfaces. In the Connect(); Visual Studio 2015 & Microsoft Azure Special Issue, I showed how to make the most of Visual C++ 2015 to easily implement IUnknown and IInspectable using the Implements class template. Now I’m going to show you how to use more of Visual C++ 2015 to implement a new ComPtr class template.
Smart pointers are notoriously difficult to write, but thanks to C++11, it’s not nearly as difficult as it once was. Part of the reason for this has to do with all of the clever tricks library developers devised to work around the lack of expressiveness in the C++ language and standard libraries, in order to make their own objects act like built-in pointers while remaining efficient and correct. In particular, rvalue references go a long way toward making life so much easier for us library developers. Another part is simply hindsight—seeing how existing designs have fared. And, of course, there’s every developer’s dilemma: showing restraint and not trying to pack every conceivable feature into a particular abstraction.
At the most basic level, a COM smart pointer must provide resource management for the underlying COM interface pointer. This implies that the smart pointer will be a class template and store an interface pointer of the desired type. Technically, it doesn’t actually need to store an interface pointer of a particular type, but could instead just store an IUnknown interface pointer, but then the smart pointer would have to rely on a static_cast whenever the smart pointer is dereferenced. This can be useful and conceptually dangerous, but I’ll talk about it in a future column. For now, I’ll begin with a basic class template for storing a strongly typed pointer:
template <typename Interface>
class ComPtr
{
public:
ComPtr() noexcept = default;
private:
Interface * m_ptr = nullptr;
};
Longtime C++ developers might wonder at first what this is all about, but chances are that most active C++ developers won’t be too surprised. The m_ptr member variable relies on a great new feature that allows non-static data members to be initialized where they’re declared. This dramatically reduces the risk of accidentally forgetting to initialize member variables as constructors are added and changed over time. Any initialization explicitly provided by a particular constructor takes precedence over this in-place initialization, but for the most part this means that constructors need not worry about setting such member variables that would otherwise have started off with unpredictable values.
Given the interface pointer is now assured to be initialized, I can also rely on another new feature to explicitly request a default definition of special member functions. In the previous example, I’m requesting the default definition of the default constructor—a default default constructor, if you will. Don’t shoot the messenger. Still, the ability to default or delete special member functions along with the ability to initialize member variables at the point of declaration are among my favorite features offered by Visual C++ 2015. It’s the little things that count.
The most important service a COM smart pointer must offer is that of shielding the developer from the perils of the intrusive COM reference-counting model. I actually like the COM approach to reference counting, but I want a library to take care of it for me. This surfaces in a number of subtle places throughout the ComPtr class template, but perhaps the most obvious is when a caller dereferences the smart pointer. I don’t want a caller to write something like what follows, accidentally or otherwise:
ComPtr<IHen> hen;
hen->AddRef();
The ability to call the AddRef or Release virtual functions should be exclusively under the purview of the smart pointer. Of course, the smart pointer must still allow the remaining methods to be called via such a dereferencing operation. Normally, a smart pointer’s dereference operator might look something like this:
Interface * operator->() const noexcept
{
return m_ptr;
}
That works for COM interface pointers and there’s no need for an assertion because an access violation is more instructive. But this implementation will still allow a caller to call AddRef and Release. The solution is simply to return a type that prohibits AddRef and Release from being called. A little class template comes in handy:
template <typename Interface>
class RemoveAddRefRelease : public Interface
{
ULONG __stdcall AddRef();
ULONG __stdcall Release();
};
The RemoveAddRefRelease class template inherits all of the template argument’s methods, but declares AddRef and Release private so that a caller may not accidentally refer to those methods. The smart pointer’s dereference operator can simply use static_cast to protect the returned interface pointer:
RemoveAddRefRelease<Interface> * operator->() const noexcept
{
return static_cast<RemoveAddRefRelease<Interface> *>(m_ptr);
}
This is just one example where my ComPtr deviates from the WRL approach. WRL opts to make all of IUnknown’s methods private, including QueryInterface, and I see no reason for restricting callers in that way. It means that WRL must inevitably provide alternatives for this essential service and that leads to added complexity and confusion for callers.
Because my ComPtr decidedly takes command of reference counting, it had better do so correctly. Well, I’ll start with a pair of private helper functions beginning with one for AddRef:
void InternalAddRef() const noexcept
{
if (m_ptr)
{
m_ptr->AddRef();
}
}
This isn’t all that exciting, but there are a variety of functions that require a reference to be taken conditionally and this will ensure I do the right thing every time. The corresponding helper function for Release is a bit more subtle:
void InternalRelease() noexcept
{
Interface * temp = m_ptr;
if (temp)
{
m_ptr = nullptr;
temp->Release();
}
}
Why the temporary? Well, consider the more intuitive but incorrect implementation that roughly mirrors what I did (correctly) inside the InternalAddRef function:
if (m_ptr)
{
m_ptr->Release(); // BUG!
m_ptr = nullptr;
}
The problem here is that calling the Release method might set off a chain of events that could see the object being released a second time. This second trip through InternalRelease would again find a non-null interface pointer and attempt to Release it again. This is admittedly an uncommon scenario, but the job of the library developer is to consider such things. The original implementation involving a temporary avoids this double Release by first detaching the interface pointer from the smart pointer and only then calling Release. Looking through the annals of history, it appears as if Jim Springfield was the first to catch this vexing bug in ATL. Anyway, with these two helper functions in hand, I can begin to implement some of the special member functions that help to make the resulting object act and feel like a built-in object. The copy constructor is a simple example.
Unlike smart pointers that provide exclusive ownership, copy construction should be allowed for COM smart pointers. Care must be taken to avoid copies at all costs, but if a caller really wants a copy then a copy is what it gets. Here’s a simple copy constructor:
ComPtr(ComPtr const & other) noexcept :
m_ptr(other.m_ptr)
{
InternalAddRef();
}
This takes care of the obvious case of copy construction. It copies the interface pointer before calling the InternalAddRef helper. If I left it there, copying a ComPtr would feel mostly like a built-in pointer, but not entirely so. I could, for example, create a copy like this:
ComPtr<IHen> hen;
ComPtr<IHen> another = hen;
This mirrors what I can do with raw pointers:
IHen * hen = nullptr;
IHen * another = hen;
But raw pointers also permit this:
IUnknown * unknown = hen;
With my simple copy constructor, I’m not permitted to do the same thing with ComPtr:
ComPtr<IUnknown> unknown = hen;
Even though IHen must ultimately derive from IUnknown, ComPtr<IHen> doesn’t derive from ComPtr<IUnknown> and the compiler considers them unrelated types. What I need is a constructor that acts as a logical copy constructor for other logically related ComPtr objects—specifically, any ComPtr with a template argument that’s convertible to the constructed ComPtr’s template argument. Here, WRL relies on type traits, but this isn’t actually necessary. All I need is a function template to provide for the possibility of conversion and then I’ll simply let the compiler check whether it’s actually convertible:
template <typename T>
ComPtr(ComPtr<T> const & other) noexcept :
m_ptr(other.m_ptr)
{
InternalAddRef();
}
It’s when the other pointer is used to initialize the object’s interface pointer that the compiler checks whether the copy is actually meaningful. So this will compile:
ComPtr<IHen> hen;
ComPtr<IUnknown> unknown = hen;
But this won’t:
ComPtr<IUnknown> unknown;
ComPtr<IHen> hen = unknown;
And that’s as it should be. Of course, the compiler still considers the two very much different types, so the constructor template won’t actually have access to the other’s private member variable, unless I make them friends:
template <typename T>
friend class ComPtr;
You might be tempted to remove some of the redundant code because IHen is convertible to IHen. Why not just remove the actual copy constructor? The problem is that this second constructor isn’t considered a copy constructor by the compiler. If you omit the copy constructor, the compiler will assume you meant to remove it and object to any reference to this deleted function. Onward.
With copy construction taken care of, it’s very important that ComPtr also provide move construction. If a move is permissible in a given scenario, ComPtr should allow the compiler to opt for that as it will save a reference bump, which is far more costly in comparison to a move operation. A move constructor is even simpler than the copy constructor because there’s no need to call InternalAddRef:
ComPtr(ComPtr && other) noexcept :
m_ptr(other.m_ptr)
{
other.m_ptr = nullptr;
}
It copies the interface pointer before clearing or resetting the pointer in the rvalue reference, or the object being moved from. In this case, however, the compiler is not so picky and you can simply eschew this move constructor for a generic version that supports convertible types:
template <typename T>
ComPtr(ComPtr<T> && other) noexcept :
m_ptr(other.m_ptr)
{
other.m_ptr = nullptr;
}
And that wraps up the ComPtr constructors. The destructor is predictably simple:
~ComPtr() noexcept
{
InternalRelease();
}
I’ve already taken care of the nuances of destruction inside the InternalRelease helper, so here I can simply reuse that goodness. I’ve discussed copy and move construction, but the corresponding assignment operators must also be provided for this smart pointer to feel like a real pointer. In order to support those operations, I’m going to add another pair of private helper functions. The first is for safely acquiring a copy of a given interface pointer:
void InternalCopy(Interface * other) noexcept
{
if (m_ptr != other)
{
InternalRelease();
m_ptr = other;
InternalAddRef();
}
}
Assuming the interface pointers are not equal (or not both null pointers), the function releases any existing reference before taking a copy of the pointer and securing a reference to the new interface pointer. In this way, I can easily call InternalCopy to take ownership of a unique reference to the given interface even if the smart pointer already holds a reference. Similarly, the second helper deals with safely moving a given interface pointer, along with the reference count it represents:
template <typename T>
void InternalMove(ComPtr<T> & other) noexcept
{
if (m_ptr != other.m_ptr)
{
InternalRelease();
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
}
While InternalCopy naturally supports convertible types, this function is a template to provide this capability for the class template. Otherwise, InternalMove is largely the same, but logically moves the interface pointer rather than acquiring an additional reference. With that out of the way, I can implement the assignment operators quite simply. First, the copy assignment, and as with the copy constructor, I must provide the canonical form:
ComPtr & operator=(ComPtr const & other) noexcept
{
InternalCopy(other.m_ptr);
return *this;
}
I can then provide a template for convertible types:
template <typename T>
ComPtr & operator=(ComPtr<T> const & other) noexcept
{
InternalCopy(other.m_ptr);
return *this;
}
But like the move constructor, I can simply provide a single generic version of move assignment:
template <typename T>
ComPtr & operator=(ComPtr<T> && other) noexcept
{
InternalMove(other);
return *this;
}
While move semantics are often superior to copy when it comes to reference-counted smart pointers, moves aren’t without cost, and a great way to avoid moves in some key scenarios is to provide swap semantics. Many container types will favor swap operations to moves, which can avoid the construction of a tremendous load of temporary objects. Implementing swap functionality for ComPtr is quite straightforward:
void Swap(ComPtr & other) noexcept
{
Interface * temp = m_ptr;
m_ptr = other.m_ptr;
other.m_ptr = temp;
}
I’d use the Standard swap algorithm but, at least in the Visual C++ implementation, the required <utility> header also indirectly includes <stdio.h> and I don’t really want to force developers into including all of that just for swap. Of course, for generic algorithms to find my Swap method, I need to also provide a non-member (lowercase) swap function:
template <typename Interface>
void swap(ComPtr<Interface> & left,
ComPtr<Interface> & right) noexcept
{
left.Swap(right);
}
As long as this is defined in the same namespace as the ComPtr class template, the compiler will happily allow generic algorithms to make use of the swap.
Another nice feature of C++11 is that of explicit conversion operators. Historically, it took some messy hacks to produce a reliable, explicit Boolean operator for checking whether a smart pointer was logically not null. Today, it’s as simple as this:
explicit operator bool() const noexcept
{
return nullptr != m_ptr;
}
And that takes care of the special and practically special members that make my smart pointer behave much like a built-in type with as much assistance as I can possibly provide to help the compiler optimize any overhead away. What remains is a small selection of helpers that are necessary for COM applications in many cases. This is where care should be taken to avoid adding too many bells and whistles. Still, there are a handful of functions on which almost any nontrivial application or component will rely. First, it needs a way to explicitly release the underlying reference. That’s easy enough:
void Reset() noexcept
{
InternalRelease();
}
And then it needs a way to get the underlying pointer, should the caller need to pass it as an argument to some other function:
Interface * Get() const noexcept
{
return m_ptr;
}
I might need to detach the reference, perhaps to return it to a caller:
Interface * Detach() noexcept
{
Interface * temp = m_ptr;
m_ptr = nullptr;
return temp;
}
I might need to make a copy of an existing pointer. This might be a reference held by the caller that I’d like to hold on to:
void Copy(Interface * other) noexcept
{
InternalCopy(other);
}
Or I might have a raw pointer that owns a reference to its target that I’d like to attach without an additional reference being procured. This can also be useful for coalescing references in rare cases:
void Attach(Interface * other) noexcept
{
InternalRelease();
m_ptr = other;
}
The final few functions play a particularly critical role, so I’ll spend a few more moments on them. COM methods traditionally return references as out parameters via a pointer to a pointer. It’s important that any COM smart pointer provide a way to directly capture such references. For that I provide the GetAddressOf method:
Interface ** GetAddressOf() noexcept
{
ASSERT(m_ptr == nullptr);
return &m_ptr;
}
This is again where my ComPtr departs from the WRL implementation in a subtle but very critical way. Notice that GetAddressOf asserts that it doesn’t hold a reference before returning its address. This is vitally important, otherwise the called function will simply overwrite whatever reference may have been held and you’ve got yourself a reference leak. Without the assertion, such bugs are much harder to detect. On the other end of the spectrum is the ability to hand out references, either of the same type or for other interfaces the underlying object might implement. If another reference to the same interface is desired, I can avoid calling QueryInterface and simply return an additional reference using the convention prescribed by COM:
void CopyTo(Interface ** other) const noexcept
{
InternalAddRef();
*other = m_ptr;
}
And you might use it as follows:
hen.CopyTo(copy.GetAddressOf());
Otherwise, QueryInterface itself can be employed with no further help from ComPtr:
HRESULT hr = hen->QueryInterface(other.GetAddressOf());
This actually relies on a function template provided directly by IUnknown to avoid having to explicitly provide the interface’s GUID.
Finally, there are often cases where an app or component needs to query for an interface without necessarily passing it back to the caller in the classic COM convention. In those cases, it makes more sense to return this new interface pointer tucked snugly inside another ComPtr, as follows:
template <typename T>
ComPtr<T> As() const noexcept
{
ComPtr<T> temp;
m_ptr->QueryInterface(temp.GetAddressOf());
return temp;
}
I can then simply use the explicit operator bool to check whether the query succeeded. Finally, ComPtr also provides all of the expected non-member comparison operators for convenience and to support various containers and generic algorithms. Again, this just helps to make the smart pointer act and feel more like a built-in pointer, all the while providing the essential services to properly manage the resource and provide the necessary services that COM apps and components expect. The ComPtr class template is just another example from Modern C++ for the Windows Runtime (moderncpp.com).
Kenny Kerr is a computer programmer based in Canada, as well as an author for Pluralsight and a Microsoft MVP. He blogs at kennykerr.ca and you can follow him on Twitter at twitter.com/kennykerr.
Thanks to the following Microsoft technical expert for reviewing this article: James McNellis