nsIWeakReference

by Scott Collins

last modified 12 August 1999

Abstract: Everything you ever wanted to know about nsIWeakReference and friends.

Document Status: Draft. There is still much work to be done; but this document needs to be public so people can start using weak references.

Why do I need this?

How do I use it?

Here's a sample. The new and interesting things are highlighted.

#include "nsWeakPtr.h"

// ...

  // it's easy to get a weak reference...
nsWeakPtr weakPtr = getter_AddRefs( NS_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 negligable, 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 NULL. Sometimes our QueryInterface through the weak pointer would return a pointer to the `real' interface, and sometimes it would return NULL. 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?

The easy way

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 { ... };

// ...

NS_IMETHODIMP
nsFoo::QueryInterface( REFNSIID aIID, void** aInstancePtr )
  {
    // ...
    else if ( aIID.Equals(nsCOMTypeInfo<nsISupportsWeakReference>::GetIID()) )
      *aInstancePr = NS_STATIC_CAST(nsISupportsWeakReference* this);
    // ...
  }

The hard way

How does it work?

Alternatives

...


Copyright© 1998-1999 by Netscape; use is subject to the NPL.