Join MDN and developers like you at Mozilla's View Source conference, 12-14 September in Berlin, Germany. Learn more at https://viewsourceconf.org

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.

なぜこれを使う必要があるのでしょうか?

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.

どのように使うのでしょうか?

サンプルをここに挙げます。新しい所と興味を引きそうな所は、強調して表現します。

#include "nsWeakPtr.h"

// ...

// 弱い参照を得るのは、簡単です...
nsWeakPtr weakPtr = getter_AddRefs( NS_GetWeakReference(aFooPtr) );

// ...

{   // ...しかし、弱い参照を使うには、(短命の) 所有する参照を必要とします。
  nsCOMPtr<nsIFoo> tempFooPtr = do_QueryReferent(weakPtr);
  if ( tempFooPtr )
    tempFooPtr->SomeFooMethod(...);
  // そうでないと、「本当の」オブジェクトが無くなってしまいます。
}

しかし、実際の例では、弱い参照はメンバー変数として保持される場合が多いでしょう。以下の例では、nsObservable は、イベントを報告するために、それぞれのオブザーバーへの参照を保持する必要があります。しかし、nsObservable は、ダングリングポインターを防ぐためだけにオブザーバーを保持するのは、望ましくありません。そのため、nsIObserver への所有する参照を保持する代わりに、弱い参照を保持します。弱い参照は、人為的にオブザーバーの生存期間を伸ばしたりしません。その上、ダングリングすることもしないのです。

以下では、渡されてくる nsIObservernsISupportsWeakReference も実装していると仮定しています。一つのオブザーバーの管理の仕方を参考にすれば、オブザーバーのリストの管理の仕方について推測することはできるでしょう。

class nsObservable
  {
    public:
      // ...
      nsresult AddObserver( nsIObserver* );
      nsresult NotifyObservers( nsIMessage* );
      // ...
    private:
      nsWeakPtr mObserver;
  // ...あるいはここにオブザーバーのリストがあると想像してください
  };

// ...

nsresult
nsObservable::AddObserver( nsIObserver* aObserver )
  {
    mObserver = getter_AddRefs( NS_GetWeakReference(aObserver) );
      // ...あるいはこれをオブザーバーのリストに追加してください
    return NS_OK;
  }

nsresult
nsObservable::NotifyObservers( nsIMessage* aMessage )
  {
    nsCOMPtr<nsIObserver> observer = do_QueryReferent(mObserver);
    if ( observer )
      observer->NoticeMessage(aMessage);
    else
      mObserver = 0;
      // あるいはこのオブザーバーをリストから取り除くことで、それはなくなってしまうでしょう
    return NS_OK;
  }

// ...

nsWeakPtrnsCOMPtr とちょうど同じインタフェースを持つことがキーになります。実は、nsWeakPtr はこのように定義されています。

typedef nsCOMPtr<nsIWeakReference> nsWeakPtr;

こりゃ最低だ!

この弱い参照の実装があなたが望むインタフェースを提供しないことに、おそらく今までに気づいたでしょう。

なぜ、弱い参照の上の私のインタフェースメソッドを直接呼べないのでしょうか?

あなたは、この弱い参照の仕組みを使って、あなたが実際に使いたいインタフェースを実装するポインターを得られることを望んでいるでしょう。例えば、

// 注: 実際にはこのような実装ではありません
nsWeakPtr<nsIFoo> weakFooPtr = fooPtr;
// ...
if ( weakFooPtr )
  status = weakFooPtr->SomeFooMethod(...);

これは、合理的な要求です。しかし、自動的に実装するのは、とても高くつくのです。継承でも、テンプレートでも、マクロでも自動的にすべてのメソッド呼び出しを実際のオブジェクトへ転送するのを支援してくれません。XPIDL は、(もし修正すれば) そのような実装を作成できるかもしれません。そうでなければ、上で議論したようなことを手で書くことができます。極わずかですが、その他のコストもあります。それは、間接呼び出しにより、呼び出しごとにかかる余分なコストです。そして単純な実装では、対象実装へのインタフェースごとに余分なポインターを追加します。

なぜペアの間で単に QueryInterface できないのでしょうか?

あなたが保持している nsIWeakReference は、本当に単に対象のオブジェクト上のインタフェースのように感じられるでしょう。二つの間で単に QueryInterface を実行したいのは、合理的なことです。なぜ余分な呼び出し GetWeakReferenceQueryReferent が必要なのでしょうか ? 弱い参照が実際に対象のオブジェクトに集約されていれば可能なように思えます。

ここでの問題は、QueryInterface です。QueryInterface は、COM が動作するのに必要な多くのことを満たす必要があります。これらの要求の元では、同じ (集約する) オブジェクトに対して同じインタフェースの QueryInterface を呼び出した場合は、同じ結果にならなければなりません。これは、どのインタフェースポインターを通じて呼び出そうとも、いつ呼び出そうとも要求されることです。我々の状況では、これは不可能です。なぜなら、それは、明らかに集約の部分を破壊可能であるという事実があるためです。後続の処理で、その部分へ到着しようとしても nsnull が返ります。時々、弱いポインターを通じた QueryInterface が「本当の」インタフェースを返すでしょう。そして時々 nsnull を返すのです。それは、単に COM を壊すことになります。

このように、弱い参照が対象オブジェクトへと集約されないのは明らかです。それため、それらの間を動くのに QueryInterface を使うことはできません。これは、より便利に感じられると思いますが、グローバルルーチンの NS_GetWeakReference を使うと、対象のオブジェクトから弱い参照を得ることができます。そして、nsIWeakReference::QueryReferent が、逆の方向に対して QueryInterface と同じ機能を提供します。付け加えると、現在、nsCOMPtr は、新しいキーの do_QueryReferent をサポートするようになり、人生をいくぶんシンプルにしてくれています。

クラスが弱い参照をサポートするようにするにはどうすればよいでしょうか?

単に nsSupportsWeakReference を継承するだけです。ミックスインクラスがすべての作業を行ってくれます。そして、あなたの QueryInterface を適宜変えてください。例えば、

//...
#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:

  • 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.

参考資料

原文書の情報

  • 著者: Scott Collins
  • 最終更新日: September 23, 2000
  • 著作権: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license | 詳細

ドキュメントのタグと貢献者

タグ: 
 このページの貢献者: kohei.yoshino
 最終更新者: kohei.yoshino,