Document Status: Public. Work continues on this document as comments and code changes are submitted.
The latest version of this document is available at
Please direct all comments and contributions to Scott Collins <scc@netscape.com> and/or news:netscape.public.mozilla.xpcom.
Update: nsCOMPtr has been updated. I am in the process of updating this documentation to match. In the mean time, the details are available in this news posting.
nsCOMPtr is a `smart-pointer'. It is a template class that acts, syntactically, just like an ordinary pointer in C or C++, i.e., you can apply * or -> to it to `get to' what it points at. nsCOMPtr is smart in that, unlike a raw COM interface pointer, nsCOMPtr manages AddRef, Release, and QueryInterface for you.
For instance, here is a typical snippet of code (at its most compact) where you assign a COM interface pointer into a member variable:
// _not_ using smart-pointers
NS_IF_RELEASE(mFoop); // If I have one already, I must
// release it before over-writing it.
if ( mFooP = aPtr ) // Now it's safe to assign it in,
// and, if it's not NULL
mFooP->AddRef(); // I must |AddRef| it, since I'll be
// holding on to it.
If our member variable mFooP were a nsCOMPtr, however, the snippet above would look like this:
// using smart-pointers
mFoop = aPtr; // ...automatically |Release|s
// the old and |AddRef|s the new
nsCOMPtr helps you write code that is leak-proof, exception safe, and significantly less verbose than you would with raw COM interface pointers. With nsCOMPtr, you may never have to call AddRef, Release, or QueryInterface by hand.
You still have to understand COM. You still have to know which functions return interface pointers that have already been AddRefed and which don't. You still have to ensure your program logic doesn't produce circularly referencing garbage. nsCOMPtr is not a panacea. It is, however, helpful, easy to use, well-tested, and polite. It doesn't require that a function author cooperate with you, nor does your use force others to use it.
Typically, you can use a nsCOMPtr exactly as you would a standard COM interface pointer:
IFoo* fooP; // ... fooP->SomeFunction(x, y, z); AnotherFunction(fooP); if ( fooP ) // ... if ( fooP == barP ) // ... |
nsCOMPtr<IFoo> fooP; // ... fooP->SomeFunction(x, y, z); AnotherFunction(fooP); if ( fooP ) // ... if ( fooP == barP ) // ... |
There are some differences, though. A nsCOMPtr differs, syntactically, from a raw COM interface pointer in three ways:
They are declared differently
// instead of saying... IFoo* fooP; |
// ...you say nsCOMPtr<IFoo> fooP; |
You can't call AddRef or Release through it, nor would you need to. AddRef is called for you whenever you assign a COM interface pointer into a nsCOMPtr. Release is called on the old value, and also when the nsCOMPtr goes out of scope. Trying to call AddRef or Release yourself will generate a compile-time error.
fooP->AddRef(); // OK fooP->Release(); // OK |
fooP->AddRef(); // Err: no perm. fooP->Release(); // Err: no perm. |
The final difference is that a bare nsCOMPtr (or rather a pointer to it) can't be supplied as an argument to a function that `fills in' a COM interface pointer. Rather it must be wrapped with a utility call that says whether the function calls AddRef before returning.
AcquireFoo(&fooP); GetFoo(&fooP); |
AcquireFoo( getter_AddRefs(fooP) ); GetFoo( getter_doesnt_AddRef(fooP) ); |
Compare the raw-pointer way...
IFoo* foo = 0;
nsresult status = CreateIFoo(&foo);
if ( NS_SUCCEEDED(status) )
{
IBar* bar = 0;
if ( NS_SUCCEEDED(status =
foo->QueryInterface(riid, &bar)) )
{
IFooBar* foobar = 0;
if ( NS_SUCCEEDED(status =
CreateIFooBar(foo, bar, &foobar)) )
{
foobar->DoTheReallyHardThing();
foobar->Release();
}
bar->Release();
}
foo->Release();
}
To the smart-pointer way...
nsCOMPtr<IFoo> fooP;
nsresult status = CreateIFoo( getter_AddRefs(fooP) );
if ( NS_SUCCEEDED(status) )
{
nsCOMPtr<IBar> barP( fooP );
if ( barP )
{
nsCOMPtr<IFooBar> fooBarP;
if ( NS_SUCCEEDED(status =
CreateIFooBar(fooP, barP,
getter_AddRefs(fooBarP))) )
fooBarP->DoTheReallyHardThing();
}
}
You should use nsCOMPtr any place you have an interface pointer that should hold a reference, i.e., that you would have called AddRef and Release on.
Don't use nsCOMPtrs in your APIs, or in such a way that your use forces them on others. Don't use an nsCOMPtr in situations where a simple pointer will do, i.e., when the pointer won't hold a reference. Don't use them in plain old C code; nsCOMPtrs are, of course, a C++ only construct.
[[more here soon]]
This section describe a couple of things that will stop you from building, and/or confuse you immediately.
There is a very common idiom for iterating over data-structures with normal pointers, e.g.,
// ...pointers to non-COM objects...
node* p = ...;
while ( p )
{
// ...
p = p->next;
}
Consider, however, what would happen if you were trying to do this with a regular COM interface pointer:
// ...raw COM interface pointers...
nsIDOMNode* child = ...;
while ( child )
{
// ...
child->GetNextSibling(&child);
// Trouble: overwrote |child| without |Release()|ing it
}
Oops! We just failed to Release(child) before putting a new pointer into it. People do this a lot, and it turns out to be a big source of leaks in normal COM code. Well, could we do this instead?
//... Release(child); child->GetNextSibling(&child); // Trouble: tried to call a member function of a dangling or |NULL| pointer //...
Unfortunately, not. After the Release, child could be dangling. In fact, if you used the NS_RELEASE macro, child would be NULL by the time you got to the GetNextSibling call.
Now imagine that you've written the same thing with nsCOMPtr.
// using |nsCOMPtr|s
nsCOMPtr<nsIDOMNode> child = ...;
while ( child )
{
// ...
child->GetNextSibling( getter_AddRefs(child) );
// Trouble: tried to call a member function of a |NULL| pointer
}
Using nsCOMPtr is exactly like the raw interface pointers, here. It Release()es and clears out child before you assign over it. By the time you get to the GetNextSibling call, child is NULL. Unlike raw interface pointers, nsCOMPtr will fire an assert() instead of blindly trying to call GetNextSibling off of a NULL pointer.
That's the problem. So what's the solution? If this were raw COM interface pointers, we'd probably introduce a temporary
// ...using raw COM interface pointers...
// ...
while ( child )
{
// ...
nsIDOMNode* temp = child;
temp->GetNextSibling(&child);
Release(temp);
}
The easiest way to do the same thing with nsCOMPtr is
nsCOMPtr<nsIDOMNode> child = //...;
while ( child )
{
// ...
nsCOMPtr<nsIDOMNode> temp = child;
temp->GetNextSibling( getter_AddRefs(child) );
}
[[the text and examples from this item came from an email exchange with Steve Clark]]
...
This section details a few (hopefully hard to get to) places where our compilers differ, allowing you to write something completely legal that will fail to compile under one of them. These patterns can be hard to catch in practice, since they are legal, and probably compile fine under the compiler you are using. If you accidentally hit one of the situations below, you'll only find out you broke the build after you check in.
VC++ can't handle the following situation
nsCOMPtr<nsIFoo> fooP; // ... if ( NS_NULL != fooP ) // ...
Instead, use the simpler C++ idiom,
if ( fooP ) // ... if ( !fooP ) // ...
VC++ < 6.0 can't handle the following situation
class nsIFoo; // forward declare some class // ... nsCOMPtr<nsIFoo> bar; // ERROR: incomplete type nsIFoo, etc.
Instead, you must make sure that you actually defined the underlying interface class, e.g.,
#include "nsIFoo.h" // fully defines |class nsIFoo| // ... nsCOMPtr<nsIFoo> bar; // no problem
Why is this? It's because VC++ tries to instantiate every member of the template as soon as it sees the template declarations. Bad compiler. No cookie! [[Thanks to mjudge, waterson, and pinkerton on this one.]]
You'll need to be careful with this, since the other compilers have no problem with this construct, it'll be easy to miss.
One of our compilers [[which one?]] can't handle the following situation
nsCOMPtr<nsIBar> bar;
// ...
if ( nsCOMPtr<nsIFoo> fooP = barP ) // ERROR: [[what is the exact error message?]]
// ...
Instead, you declare your nsCOMPtr outside the expression, e.g.,
nsCOMPtr<nsIBar> bar; // ... nsCOMPtr<nsIFoo> fooP = barP; if ( fooP ) // ...
A default constructor is one that can be called with no arguments. It is a general guideline of COM that the real construction happens in a separate function, so most COM classes have default constructors. However, if you make an nsCOMPtr for a class without a default constructor, applying operator->() causes a compile-time error on the Macintosh.
nsCOMPtr<nsIBar> bar; // some class without a default constructor
// ...
barP->SomeMemberFunc(); // ERROR: [[what is the exact error message?]]
You must ensure that all COM interface classes supply default constructors.
getter_AddRefs and getter_doesnt_AddRef use underscores for the same reason our special macros do, quoting from our coding conventions "...to make them stick out like a sore thumb". Note also that since AddRef is one word, getter_AddRefs and getter_doesnt_AddRef couldn't have the right spacing if only inter-caps were used.
Yes, but you'll have to abide by the NPL, and in the code you'll have to:
[[Fill in details and links for the following
]]