Ccomptr Assignment

The latest version of this topic can be found at CComPtr Class.

A smart pointer class for managing COM interface pointers.

Parameters


A COM interface specifying the type of pointer to be stored.

Public Constructors

Public Operators

ATL uses and CComQIPtr to manage COM interface pointers. Both are derived from CComPtrBase, and both perform automatic reference counting.

The CComPtr and CComQIPtr classes can help eliminate memory leaks by performing automatic reference counting. The following functions both perform the same logical operations; however, note how the second version may be less error-prone by using the CComPtr class:

In Debug builds, link atlsd.lib for code tracing.

CComPtrBase

Header: atlbase.h

The constructor.

Parameters


Used to initialize the interface pointer.


A COM interface.

Assignment operator.

Return Value

Returns a pointer to the updated object

Remarks

This operation AddRefs the new object and releases the existing object, if one exists.

CComPtr::CComPtr
CComQIPtr::CComQIPtr
Class Overview

template<class T> class CComPtr
// Error-checking routine that performs manual lifetime management// of a COM IErrorInfo object HRESULT CheckComError_Manual() { HRESULT hr; CComBSTR bstrDescription; CComBSTR bstrSource; CComBSTR bstrHelpFile; IErrorInfo* pErrInfo = NULL; // naked COM interface pointer hr = ::GetErrorInfo(0, &pErrInfo); if(hr != S_OK) return hr; hr = pErrInfo->GetDescription(&bstrDescription); if(FAILED(hr)) { pErrInfo->Release(); // must release interface pointer before returningreturn hr; } hr = pErrInfo->GetSource(&bstrSource); if(FAILED(hr)) { pErrInfo->Release(); // must release interface pointer before returningreturn hr; } hr = pErrInfo->GetHelpFile(&bstrHelpFile); if(FAILED(hr)) { pErrInfo->Release(); // must release interface pointer before returningreturn hr; } pErrInfo->Release(); // must release interface pointer before returningreturn S_OK; }
// Error-checking routine that performs automatic lifetime management// of a COM IErrorInfo object through a CComPtr smart pointer object HRESULT CheckComError_SmartPtr() { HRESULT hr; CComBSTR bstrDescription; CComBSTR bstrSource; CComBSTR bstrHelpFile; CComPtr<IErrorInfo> pErrInfo; hr = ::GetErrorInfo(0, &pErrInfo); if(hr != S_OK) return hr; hr = pErrInfo->GetDescription(&bstrDescription); if(FAILED(hr)) return hr; hr = pErrInfo->GetSource(&bstrSource); if(FAILED(hr)) return hr; hr = pErrInfo->GetHelpFile(&bstrHelpFile); if(FAILED(hr)) return hr; return S_OK; } // CComPtr will auto-release underlying IErrorInfo interface pointer as needed
CComPtr() throw (); CComPtr(T* lp) throw (); CComPtr (const CComPtr<T>& lp) throw ();
T* operator= (T* lp) throw (); T* operator= (const CComPtr<T>& lp) throw ();

Last time, I presented a puzzle regarding a memory leak. Here's the relevant code fragment:

CComPtr<IStream> pMemoryStream; CComPtr<IXmlReader> pReader; UINT nDepth = 0; //Open read-only input stream pMemoryStream = ::SHCreateMemStream(utf8Xml, cbUtf8Xml);

The problem here is assigning the return value of to a smart pointer instead of attaching it.

The function creates a memory stream and returns a pointer to it. That pointer has a reference count of one, in accordance with COM rules that a function which produces a reference calls , and the responsibility is placed upon the recipient to call . The assignment operator for is a copy operation: It s the pointer and saves it. You're still on the hook for the reference count of the original pointer.

ATLINLINE ATLAPI_(IUnknown*) AtlComPtrAssign(IUnknown** pp, IUnknown* lp) { if (lp != NULL) lp->AddRef(); if (*pp) (*pp)->Release(); *pp = lp; return lp; } template <class T> class CComPtr { public: ... T* operator=(T* lp) { return (T*)AtlComPtrAssign((IUnknown**)&p, lp); }

Observe that assigning a to a s the incoming pointer and s the old pointer (if any). When the is destructed, it will release the pointer, undoing the that was performed by the assignment operator. In other words, assignment followed by destruction has a net effect of zero on the pointer you assigned. The operation behaves like a copy.

Another way of putting a pointer into a is with the operator. This is a transfer operation:

void Attach(T* p2) { if (p) p->Release(); p = p2; }

Observe that there is no here. When the is destructed, it will perform the , which doesn't undo any operation performed by the . Instead, it releases the reference count held by the original pointer you attached.

Let's put this in a table, since people seem to like tables:

OperationBehaviorSemantics
Attach()Takes ownershipTransfer semantics
operator=()Creates a new referenceCopy semantics

You use the method when you want to assume responsibility for releasing the pointer (ownership transfer). You use the assignment operator when you want the original pointer to continue to be responsible for its own release (no ownership transfer).

There is also a method which is the opposite of : Detaching a pointer from the means "I am taking over responsibility for releasing this pointer." The gives you its pointer and then forgets about it; you're now on your own.

The memory leak in the code fragment above occurs because the assignment operator has copy semantics, but we wanted transfer semantics, since we want the smart pointer to take the responsibility for releasing the pointer when it is destructed.

pMemoryStream.Attach(::SHCreateMemStream(utf8Xml, cbUtf8Xml));

The method is definitely one of the more dangerous methods in the repertoire, because it's so easy to assign a pointer to a smart pointer without giving it a moment's thought. (Another dangerous method is the , but at least that has an assertion to try to catch the bad usages. Even nastier is the secret QI'ing assignment operator.) I have to say that there is merit to Ben Hutchings' recommendation simply not to allow a simple pointer to be assigned to a smart pointer, precisely because the semantics are easily misunderstood. (The boost library, for example, follows Ben's recommendation.)

Here's another exercise based on what you've learned:

Application Verifier told us that we have a memory leak, and we traced it back to the function .

BSTR GetInnerText(IXMLDOMNode *node) { BSTR bstrText = NULL; node->get_text(&bstrText); return bstrText; } DWORD GetTextAsInteger(IXMLDOMNode *node) { DWORD value = 0; CComVariant innerText = GetInnerText(node); hr = VariantChangeType(&innerText, &innerText, 0, VT_UI4); if (SUCCEEDED(hr)) { value = V_UI4(&innerText); } return value; }

Obviously, the problem is that we passed the same input and output pointers to , causing the output integer to overwrite the input , resulting in the leak of the . But when we fixed the function, we still got the leak:

DWORD GetTextAsInteger(IXMLDOMNode *node) { DWORD value = 0; CComVariant innerText = GetInnerText(node); CComVariant textAsValue; hr = VariantChangeType(&innerText, &textAsValue, 0, VT_UI4); if (SUCCEEDED(hr)) { value = V_UI4(&textAsValue); } return value; }

Is there a leak in the function itself?

Hint: It is in fact explicitly documented that the output parameter to can be equal to the input parameter, which results in an in-place conversion. There was nothing wrong with the original call to .

0 comments

Leave a Reply

Your email address will not be published. Required fields are marked *