Weak reference

In XPCOM, a weak reference is a special object that contains a pointer to an XPCOM object, but does not keep that object alive. If the referent object is destroyed before the weak reference, the pointer inside the weak reference is set to nsnull.

Why do I need this?

When you hold an owning reference on an object (i.e., you have AddRefed 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 of weak 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.

How do I use it?

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;

Well this sucks!

By now you've probably noticed that this particular weak reference implementation doesn't give you exactly the interface you were hoping for.

Why can't I just directly call my interfaces methods on the weak reference?

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.

Why can't I just 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.

How do I make a class support weak references?

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);
    // ...
  }

Alternatives

This technique is useful, but in situations where you need this, there are two alternatives which you may want to consider:

  • You might hold an owning reference, but arrange to Release it out-of-band; this must be before the destructor, which would otherwise never be called.
  • You might hold a raw pointer (without AddRefing and Releaseing it), and avoid using it in cases where it might dangle.

See also

Original Document Information

  • Author: Scott Collins
  • Last Updated Date: September 23, 2000
  • Copyright Information: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license | Details.
 

Document Tags and Contributors

Tags: 
 Contributors to this page: teoli, AdamRoach, gsvelto, Neil, Shaver, Mgjbot, Nickolay, Jorend
 Last updated by: AdamRoach,