In XPCOM, a weak reference is a special object that contains a pointer to an XPCOM object, but doesnot keep that object alive. If the referent object is destroyed before the weak reference, the pointer inside the weak reference is set to nsnull
.
When you hold an owning reference on an object (i.e., you have AddRef
ed it), you are holding that object in existence for the duration of your reference. This isn't always appropriate, and can lead to trouble. If, for instance, this owning reference is part of a cycle of owning references (e.g., if the referenced object also holds a owning reference back to you), then none of the objects in the cycle can be reclaimed without taking special measures.
There are less severe situations. A naive design for an observer/observable relationship would require the observable to hold a owning reference to the observer. After all, the observable must send messages to each observer, notifying it of the appropriate state changes. To do that, it will call a method on the observer, so it needs a pointer. And the call would fail if the pointer were allowed to dangle, therefore, the pointer should be a owning reference. This design, however, is flawed, as it holds the observer in existence longer than would otherwise be necessary. The observer may only need a short life compared to the thing being observed. It might go away after the first interesting event, even. But in this design, by hitching its life to the observable, it is kept on life-support long past any need or use.
What is actually needed in this case, is either out-of-band signaling, where when the observer wants to go away, it unregisters itself from the observable, which then releases its owning reference, allowing the observer to die, or else a new kind of reference. This document describes an implementation ofweak references. A weak reference does not hold its referent in existence, but also will not dangle. When the referent is destroyed, the weak reference automatically becomes nsnull
. This technique can significantly simplify certain relationships, and you should consider it when an owning reference is inappropriate, but where a raw pointer might end up dangling.
Here's an example. The new and interesting things are highlighted.
#include "nsWeakPtr.h" #include "nsIWeakReferenceUtils.h" // ... // it's easy to get a weak reference... nsWeakPtr weakPtr = do_GetWeakReference(aFooPtr); // ... { // ...but to use my weak reference, I'll need a (short-lived) owning reference nsCOMPtr<nsIFoo> tempFooPtr = do_QueryReferent(weakPtr); if ( tempFooPtr ) tempFooPtr->SomeFooMethod(...); // else, the `real' object has gone away }
In a real world example, however, you are more likely to be holding a weak reference in a member variable. In the following example, an nsObservable
must keep some kind of a reference to each observer, in order to report events. The nsObservable
doesn't want to keep the observers alive just to prevent a dangling pointer, however. So, instead of holding an owning reference to an nsIObserver
, it holds a weak reference. The weak reference doesn't artificially extend the life of the observer, and yet, it can never dangle.
The following assumes that any nsIObserver
that is passed in also implements nsISupportsWeakReference
. You can extrapolate from managing a single observer to managing a list of observers.
class nsObservable { public: // ... nsresult AddObserver( nsIObserver* ); nsresult NotifyObservers( nsIMessage* ); // ... private: nsWeakPtr mObserver; // ...or imagine a list of observers here }; // ... nsresult nsObservable::AddObserver( nsIObserver* aObserver ) { mObserver = getter_AddRefs( NS_GetWeakReference(aObserver) ); // ...or append this to the list of observers return NS_OK; } nsresult nsObservable::NotifyObservers( nsIMessage* aMessage ) { nsCOMPtr<nsIObserver> observer = do_QueryReferent(mObserver); if ( observer ) observer->NoticeMessage(aMessage); else mObserver = 0; // or remove this observer from the list, he's gone away return NS_OK; } // ...
It's key to note that an nsWeakPtr
has exactly the same interface as an nsCOMPtr
. In fact, nsWeakPtr
is defined like this
typedef nsCOMPtr<nsIWeakReference> nsWeakPtr;
By now you've probably noticed that this particular weak reference implementation doesn't give you exactly the interface you were hoping for.
You really want this weak reference scheme to give you a pointer that implements the interface you actually care about, e.g.,
// Note: _not_ the implementation we have... nsWeakPtr<nsIFoo> weakFooPtr = fooPtr; // ... if ( weakFooPtr ) status = weakFooPtr->SomeFooMethod(...);
This is a reasonable thing to want. It's expensive to implement automatically, however. Neither inheritance, nor templates, nor macros can help automatically forward all the method calls to the real object. XPIDL could write an implementation for you (if we modified it), or you could write one by hand as I discuss below. There are other, mostly negligible, costs: it's an extra indirection per call, and the easy implementation requires adding an extra pointer per interface to the target implementation.
QueryInterface
between the pair?It really feels like the nsIWeakReference
that you are holding is just another interface on the target object. It seems reasonable to want to simply QueryInterface
between the two. Why these extra calls: GetWeakReference
and QueryReferent
? This would be possible if the weak reference was actually aggregated to the target object.
The problem here is QueryInterface
. QueryInterface
must satisfy many requirements to allow COM to work. Among these requirements is that every call to QueryInterface against the same (aggragate) object for the same interface must yield the same result, no matter what interface pointer you call it through, and no matter when you call it. This is impossible in our situation, since we explicitly rely on the the fact that part of the `aggregate' can be destroyed. Subsequent attempts to reach that part must return nsnull
. Sometimes our QueryInterface
through the weak pointer would return a pointer to the `real' interface, and sometimes it would return nsnull
. We just broke COM.
It's clear, therefore, that the weak reference can't be aggregated to the target object. Hence, we can't use QueryInterface
to move between them. I know this sounds more convenient, but the global routine NS_GetWeakReference
makes it easy to go from the target object to the weak reference; and nsIWeakReference::QueryReferent
gives you the same functionality as QueryInterface
for getting back. Additionally, nsCOMPtr
now supports the new key do_QueryReferent
to simplify life even further.
In JavaScript, just make sure that your QueryInterface
method returns your object for the nsISupportsWeakReference
interface, and you're set; XPConnect does all the work for you.
Just roll in nsSupportsWeakReference
, a mix-in class that does all the work, and adjust your QueryInterface
accordingly, e.g.,
//... #include "nsWeakReference.h" class nsFoo : public nsIFoo, ..., public nsSupportsWeakReference { ... }; // ...if you used the table macros to implement |QueryInterface|, add an entry NS_INTERFACE_MAP_BEGIN(nsFoo) // ... NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) // ... NS_INTERFACE_MAP_END // ...if you used a |NS_IMPLE_QUERYINTERFACEn| macro, move up to the next higher one and add a term NS_IMPL_QUERYINTERFACEn+1( ...,nsISupportsWeakReference) // ...if you implemented |QueryInterface| by hand, add a clause NS_IMETHODIMP nsFoo::QueryInterface( REFNSIID aIID, void** aInstancePtr ) { // ... else if ( aIID.Equals(nsCOMTypeInfo<nsISupportsWeakReference>::GetIID()) ) *aInstancePr = NS_STATIC_CAST(nsISupportsWeakReference*, this); // ... }
This technique is useful, but in situations where you need this, there are two alternatives which you may want to consider:
Release
it out-of-band; this must be before the destructor, which would otherwise never be called.AddRef
ing and Release
ing it), and avoid using it in cases where it might dangle.