Please note, this is a STATIC archive of website developer.mozilla.org from November 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

Reference Manual

このセクションは、あなたが、すでに nsCOMPtr に慣れ親しんでいるけれども、もっと詳細な事柄を知りたい場合に役立つでしょう。もし、まだ nsCOMPtr を前に使っていないのであれば、まず スタートガイド を読みたいかもしれません。もし、壊れたビルドを直そうとしているのであれば、FAQ によって、もっと素早く答えを得られるかもしれません。

基本

設計

nsCOMPtr は、所有する参照として使われる所で、生の [XP]COM インタフェースポインタを完全に置き換えるように設計されました。生の [XP]COM インタフェースポインタを使うことができるところであれば、ほとんどの場合、nsCOMPtr を使うことができるはずです。nsCOMPtrは、生の [XP]COM インタフェースポインタと正確な同じ大きさと形です。それは、容量を食いすぎることもなく、メンバ変数として使うことができます。

所有する参照のほとんどの作業は、nsCOMPtr のコンストラクタ、デストラクタと代入演算子で行われます。あなたが(代入や初期化により) nsCOMPtr を異なる [XP]COM オブジェクトで「指す」場合、もし古い値があれば、それを Release しなければなりません。そして、新しい値を AddRef しなければなりません。自分のデストラクタ実行時には、同様に Release しなければなりません。nsCOMPtr は、もしあなたがいつも正しいことを覚えているのであれば、ちょうどあなたがしていた作業しかしません。

安全性の特徴

型の保護手段

元の型のための正しい [XP]COM インタフェースポインタを保持しているのは、nsCOMPtr の不変性です。例えば、nsCOMPtr<nsIFoo> は、[XP]COM オブジェクトに nsIFoo インタフェースを問い合わせる時に、code>QueryInterface</code> によって返されるポインタを常に保持しています。デバッグビルドでは、もし代入時に QueryInterface を呼ばずに、この不変性を覆すと、nsCOMPtr は、間違った代入として実行時にアサートするでしょう。

// 二つの無関係なインタフェース |nsIFoo| と |nsIBar| があるものとします...
nsIBar* bar = ...;
// ...

nsCOMPtr<nsIFoo> foo = bar;
  // NS_ASSERTION: "QueryInterface needed"

  // ...あなたは、この行をコンパイルできるとみなすかもしれませんが
  // (キャストでもそうです。なぜなら型が C に関係づけられているからです)

この不変性は、nsCOMPtr<nsISupports> では、緩められます。nsISupports*(あるいはvoid*)のように、人々は一般的にnsCOMPtr<nsISupports> を「任意の [XP]COM インタフェース」とみなして使います。もし実際の型を気にしないようなオブジェクトに対して、nsCOMPtr が [XP]COM として正しい nsISupportsQueryInterface することを強制するのであれば、煩わしいかもしれません。

NULL 間接参照の保護手段

もし中が空の時に間接参照しようとすると、nsCOMPtr は、実行時にアサートします。例えば、

nsCOMPtr<nsIFoo> foo;
  // 注: デフォルトでは、|0| に初期化されます。

foo->DoSomething();
  // NS_PRECONDITION: "You can't dereference a NULL nsCOMPtr with operator->()"

同様の事前条件が operator* のために、介在します。

参照カウントの保護手段

nsCOMPtr から元の生のポインタを取り出すすべての操作に対して、安全な特徴を実装するための C の別のトリックを使います。返ってきたポインタに対して、AddRefReleasedeleteを実行することができません。

nsCOMPtr<nsIFoo> foo = ...;

foo->AddRef();     // エラー: |AddRef| はプライベートです。
delete foo.get();  // エラー: |operator delete| はプライベートです。
NS_RELEASE(foo);   // エラー: |Release| はプライベートです。

もちろん、nsCOMPtr によって提供される安全性に関する最も重要な特徴は、それが適切な時期に自動的に AddRefRelease を実行することです。

キャスト

nsCOMPtr では、旧式の C のキャストを使わないでください。旧式のキャストは、たとえそれが正しくないとしても、コンパイルできることが保障されています。旧式のキャストは、もし変換が定義されていないのであれば、reinterpret_cast と同じものにしてしまいます。そのようなキャストは、nsCOMPtr の機構を容易にバイパスしてしまい、リークの発生、型の不一致、その他の不幸な出来事を招きます。

// 旧式の C のキャストが |nsCOMPtr| 機構をバイパスし、リークを発生させる...

nsresult rv;
nsCOMPtr<nsIFoo> foo = ...;

// ...
rv = GetFoo( (nsIFoo**)&foo );
rv = GetFoo( &(nsIFoo*)foo );
  // もちろん、これらはコンパイルできます。でもリークしてしまいます。

これを防ぐのを助けるために、上記のように operator&private にすることにより、最初の形を不法とすることを試みます。バグ 59414 を参照してまださい。

実装の詳細とデバッグ機構

nsCOMPtr はクラスですが、virtual なメソッドを持っていません。つまり、vtable または vptr を持っていません。キーとなるいくつかのルーチンが共通の非テンプレートの基底クラスに分解されるので、実際の元のポインタは、nsISupports* として保存されます (ただしデバッグビルドで NSCAP_FEATURE_DEBUG_PTR_TYPES がオンになっている場合は除きます)。それは、これらの分解されたルーチンのために、nsCOMPtr のユーザが XPCOM ライブラリとリンクしなければいけないからです。

NSCAP_FEATURE_DEBUG_PTR_TYPES がオンになっている時、nsISupports* 型の変数に元のポインタを保持する代わりに、nsCOMPtr は、元の型に適合するポインタに保持します。これにより、ソースレベルのデバッガがより簡単にポインタを「追跡」できるようになります。しかしながら、基底クラスに分解されるルーチンは、今やテンプレート特有のインラインコードとなります。分解される基底クラスはありません。これは、すべてのアプリケーションが NSCAP_FEATURE_DEBUG_PTR_TYPES について同じ設定でコンパイルされなければならないことを意味します。そうでないと、いくつかの部分では基底クラスを期待し、他の部分ではそうでないことを期待することになります。アプリケーションは、リンクできないでしょう。

ユニットテスト

nsCOMPtrのためのユニットテストは、このファイルにあります。

初期化と代入

組み込み形式

nsCOMPtr への代入や初期化は、簡単に理解できます。nsCOMPtr は、その古い値がもしあれば Release し、そして新しい値を代入し、AddRef を呼び出し、および/または、あなたが直接「注釈」することによって dont_AddRef のような指示子で代入する QueryInterface を呼び出します。このセクションでは、それぞれ起こりうる場合を記述します。ただし、指示子については、より簡潔に以下の表で記述しています。

あなたは、nsCOMPtr を以下のものから構築するか、以下のものから代入することができます。

  • 0
  • 同じ型の他の nsCOMPtr
  • 同じ型の生の [XP]COM インタフェースポインタ
  • 同じ型の生の [XP]COM インタフェースポインタで、かつ dont_QueryInterface 指示子により注釈されたもの。
  • 同じ型の生の [XP]COM インタフェースポインタで、かつ dont_AddRef 指示子や同様のものにより注釈されたもの。
  • 任意の型の任意のインタフェースポインタ (nsCOMPtr でも、生の [XP]COM インタフェースポインタでも) で、かつ do_QueryInterface 指示子により注釈されたもの。
  • do_QueryReferent 指示子

最初の三つは、単純で明らかです。4 番目のもの (dont_QueryInterface 指示子の適用) は、同じ型の生の [XP]COM インタフェースポインタの代入と同じことです。残りの指示子は、特殊な状況において、いくつかの付加的な制御を提供するものです。さらに、nsCOMPtr を初期値なしで構築することができ、その場合は、0 で初期化されます。ちょうどプリミティブなポインタのように、値が0nsCOMPtr は、どのオブジェクトも指しません。そして、if (foo)if (!foo) のように式をテストすることができます。

上述した指示は、この表によりもっとはっきりするでしょう。

表 1. nsCOMPtrに代入するオプション
QueryInterface しない QueryInterface する
AddRef する

T*,
dont_QueryInterface(T*)

do_QueryInterface(nsISupports*),
do_QueryInterface(nsISupports*, nsresult*)
do_QueryReferent(nsIWeakReference*),
do_QueryReferent(nsIWeakReference*, nsresult*)

AddRef しない

dont_AddRef(T*),
getter_AddRefs(T*)

n/a

例えば、nsCOMPtr への代入において、(なんらかの理由ですでに AddRef を実行したために)代入するポインタに対して AddRef を実行したくない場合、「AddRefしない」と「QueryInterface しない」の交差する所にある dont_AddRef(T*) を使うのは一つの可能性です。以下に、dont_AddRefを使い、様々な位置にそれらの「注釈」が表れるサンプルを示します。

// |nsCOMPtr| への代入を管理する...

  // コンストラクタにおいて...
nsCOMPtr<nsIFoo> foo1( dont_AddRef(rawFoo1Ptr) );
nsCOMPtr<nsIFoo> foo2 = dont_AddRef(rawFoo2Ptr);
   // (直接の初期化と呼ばれる) 関数の形式と (コピーの初期化と呼ばれる)
  //  コンストラクタの代入の形式は、微妙に異なる意味を
  //  持つことに注意してください。直接の初期化の方が好ましい。

nsCOMPtr<nsIFoo> foo3;

  // 通常の代入において...
foo3 = dont_AddRef(rawFoo3Ptr);
 
   // 表で記述されている注釈をコンストラクタと
  //  単純で古いタイプの代入に適用しています。

表に示されたどの注釈も dont_AddRef() を使って示されたすべての場所に出現可能です。続くセクションでそれぞれの可能性を記述します。

nsCOMPtr<T> = T*,
nsCOMPtr<T> = dont_QueryInterface( T* )

表で T* として示されるデフォルトの振舞いでは、新しい値に対して、AddRef を実行します。しかし、それに対して、QueryInterface は実行しません。「注釈」がない時に何が起きるかという例を示します。例えば、

nsCOMPtr<nsIFoo> foo( aFooPtr ); // もしくは
foo = aFooPtr;
  // ...|AddRef| は呼び出しますが、|QueryInterface| は呼び出しません。|

  // 同じものをより明示的に表すと...
nsCOMPtr<nsIFoo> foo( dont_QueryInterface(aFooPtr) ); // もしくは
foo = dont_QueryInterface(aFooPtr);
 

この形式を使うことにより、あなたが代入しているポインタがすでに、nsCOMPtr の元の型、この場合は、nsIFoo に適合している、[XP]COMとして正しいインタフェースへのポインタであることを約束していることになります。

nsCOMPtr<T> = do_QueryInterface( nsISupports* ),
nsCOMPtr<T> = do_QueryInterface( nsISupports*, nsresult* )

もし、上記の約束を果たせない時は、nsCOMPtr に対して、代入において QueryInterface を呼び出す必要があると、「注釈」をすることができます。例えば、

nsCOMPtr<nsIFoo> foo( do_QueryInterface(aBarPtr) ); // もしくは
foo = do_QueryInterface(aBarPtr);
   // ...|QueryInterface| が呼ばれる _でしょう_。(その結果 |AddRef| も呼ばれます)

  // もちろん、|QueryInterface| を呼んでいるので、
  //  エラー結果も必要になるでしょう...
nsresult rv;
nsCOMPtr<nsIFoo> foo( do_QueryInterface(aBarPtr, &rv) ); // もしくは
foo = do_QueryInterface(aBarPtr, &rv);
 
nsCOMPtr<T> = dont_AddRef( T* ),
nsCOMPtr<T> = getter_AddRefs( T* )

時々、すでに AddRef が実行されたポインタをたまたま持っていて、それを nsCOMPtr に代入したい場合があるでしょう。これは、しばしば、(nsresult を結果とするのではなく) AddRef が実行されたポインタを結果として返す getter を使った時に起きます。あるいは、効率性のための変形により起きる場合もあります。dont_AddRef は、このような場合の完璧な治療法です。

nsIFoo* temp;
nsresult rv = GetFoo(&temp);
nsCOMPtr<nsIFoo> foo( dont_AddRef(temp) );
  // |temp| はすでに |AddRef| を実行済ですが、我々はこれを
  //  |nsCOMPtr| で管理しようとしています。.

nsCOMPtr<nsIFoo> foo( getter_AddRefs(CreateAFoo()) );
  // |getter_AddRefs| は |dont_AddRef| の同意語です。
  //  これは、|AddRef| が実行されたポインタを返す関数に適用する時に分かりやすくするものです。

nsCOMPtr<nsIFoo> foo( dont_AddRef(CreateAFoo()) );
  // あるいは、あなたはそれが好きではないかもしれません...
nsCOMPtr<T> = /* QueryInterface を呼び出しますが、AddRef は呼び出しません。 */

表のこの象限が「n/a (not applicable)」とマークされているのに気づくでしょう。「QueryInterface を呼び出すが、AddRef を行わないこと」を意味する明示的な指令はありません。このオプションは、間違った型のオブジェクトを返す getter を呼び出す状況に対応します。すでに AddRef を実行したオブジェクトを持っているので、もう AddRef を実行したくないが、違うインタフェースを得る必要がある場合です。それはできません。QueryInterface は、常に AddRef をその結果に対して実行します。そして、正しい型を得るための QueryInterface の呼び出しの代用品は存在しません。解決するには、2 段階のプロセスを実行します。

// ...

  // getter は (間違った型の) すでに |AddRef| を実行したオブジェクトを返します...
nsCOMPtr<nsIBar> bar( getter_AddRefs(CreateBar()) );
  // ...(このオブジェクトに対して) 正しい型を問い合わせる必要があります。
nsCOMPtr<nsIFoo> foo( do_QueryInterface(bar) );

この場合において、人々が陥る不運なワナは、getter 関数が結果を AddRef していることを忘れることです。こんな感じのコードをタイプしてしまいます:

nsCOMPtr<nsIFoo> foo( do_QueryInterface(CreateBar()) );
  // おっと! |CreateBar| によって返ってくるインタフェースがリークしてしまいます。
  //  この場合、あなたは上に示した二つのステップの解決法で処理する_必要_があります。

  // ありそうもないですか? こんな感じの形で見ることはあるでしょう。
nsCOMPtr<nsIFoo> foo( do_QueryInterface(aList->ElementAt(i)) );
  // すべての良い getter のように、|ElementAt| は、
  // インタフェースからの必要性に応じて問い合わせを行なった後、
  // 破棄されるかも知れない結果を |AddRef| します。

Bugzilla バグ 8221 は、この特定のリークの発見と修正に限定されたものです。

nsCOMPtr ヘルパー

nsCOMPtr<T> = do_QueryReferent( nsIWeakReference* ),
nsCOMPtr<T> = do_QueryReferent( nsIWeakReference*, nsresult* )

nsIWeakReference に基づく弱い参照を容易にする do_QueryReferent というのがあります。nsIWeakReference は、他のオブジェクトのプロキシとして振舞う [XP]COM オブジェクトです。nsIWeakReference と (上記の) 他のオブジェクトは、特別な関係にあります。それらは、お互いのことを知っています。しかし、どちらももう一方への所有する参照を保持していません。二つのオブジェクトは、もう一方へのダングリングポインタを持たないことを保障するように協調しています。nsIWeakReference オブジェクトにおいて所有する参照を保持することにより、この他のオブジェクトを必要な時に得ることができ、しかし、それ (他のオブジェクト) が生きていなくてもよいのです。そのオブジェクトを得るためには、nsIWeakReference オブジェクトに、あなたの代わりに QueryInterface するように依頼します。もしオブジェクトがまだ存在しており、要求されたインタフェースをサポートしているのであれば、あなたは (できれば、一時的に) それに対する所有する参照を持つことができます。

nsIWeakReference* weakPtr = ...;

weakPtr->QueryReferent(

T* として nsCOMPtr<T> を使う

nsCOMPtrをポインタとして使う

「入力」パラメタ

「出力」パラメタ: getter_AddRefs

nsCOMPtr への代入は、とても理解しやすいです。nsCOMPtr は、古い値がもしあれば、それを Release します。そして、代入した新しい値を AddRef し、および/または上述した指令に示されたQueryInterface を呼び出します。これらの規則は、nsCOMPtr として宣言されたパラメタや関数の復帰値のコピーにおいて起こる「代入」でも同じく適用されます。もし nsCOMPtr を生の [XP]COM インタフェースポインタの実用的な代用品としたいのであれば、しかしながら、「出力」パラメタの問題に対処する必要があります。多くの [XP]COM 関数は、結果のインタフェースポインタをパラメタを通じて返します。例えば、

// Getter は、インタフェースポインタを「出力」パラメタを通じて返すことができます。...

nsresult GetFoo( nsIFoo** );     // 標準的 getter
nsresult GetFoo2( nsIFoo*& );    // 非標準的 getter
nsresult GetSomething( void** ); // 「型無し」の getter
  // 注: |QueryInterface| は、「型無し」の getter の例です。

我々は、「出力」パラメタを使うルーチンへポインタや参照によって nsCOMPtr を渡せなければいけません。問題は、getter 内部には、nsCOMPtr に対する情報がないことです。それは、生の [XP]COM インタフェースポインタへのポインタ (または参照) を得ていると考えます。nsCOMPtr のスマートな代入演算子は、呼ばれません。古い値があれば、リークしてしまいます。

ここで、getter_AddRefs( nsCOMPtr& )が役に立ちます。getter_AddRefs は、古い値があれば Release し、それをクリアします。そして、それに対するポインタを返し、getter は nsCOMPtrAddRef を実行した新しい値を設定します。我々は、これらの状況で、生の [XP]COM インタフェースポインタに適用していた & を置き換えるものとして、getter_AddRef を使用します。getter_AddRefs は、通常 nsCOMPtr のコンストラクタと代入演算子から得ていた魔法を詰めこんだものです。

// 生の [XP]COM インタフェースポインタ...

nsIFoo* foo;

GetFoo(&foo);
GetFoo2(foo);
GetSomething((void**)&foo);
// |nsCOMPtr|...

nsCOMPtr<nsIFoo> foo;

GetFoo(getter_AddRefs(foo));
GetFoo2(*getter_AddRefs(foo));
GetSomething(getter_AddRefs(foo));

これを実現するのに、なぜ単に operator& をオーバーロードしないのでしょうか? いくつかの理由: 他の状況では、nsCOMPtr のアドレスを取るのは、不便なことになります。「getter_AddRefs」という名前は、getter としてある一定の振舞いを強制します。そして、かつては、他の可能性がありました (あなたがまさに学ぼうとしているように)。

パラメタを通じて復帰値を返しますが、AddRef を実行していない getter のために、getter_doesnt_AddRef( nsCOMPtr& ) というのは、ありますか? いいえ、ありません。かつてありましたが、それは 3 つの理由でなくなりました:

  • getter がパラメタを通じて AddRef を実行していないインタフェースポインタを返すのは、[XP]COM の規則に反しています。(もしそれを見つけたら、バグ報告をしてください。)
  • getter_doesnt_AddRef は、nsCOMPtr を生の [XP]COM インタフェースポインタよりも大きく、または遅くしてしまう複雑な波及効果があります。
  • とりあえず、そのような getter を呼んで、一時的にnsCOMPtrに結果を入れることもできます。例えば、
// その結果に対して、|AddRef| を (違法に) 呼び出さない getter を呼び出します...

nsIFoo* temp;
nsresult rv = GetFoo_WithoutAddRef(&temp);
   // 自分への注: |GetFoo_WithoutAddRef| をバグとして報告しなければならない。
  // すべての getter は、 |AddRef| しなければならない。
nsCOMPtr<nsIFoo> foo = temp;

「入出力」パラメタ

「入力/出力」パラメタについては、どうなんでしょう?

効率性と正確性

nsCOMPtrのコスト

nsCOMPtr は、生の [XP]COM インタフェースポインタに対する実用的な置き換えとなるべく調整されています。所有する参照として使うのであれば、どの場所においてもです。nsCOMPtrs のパフォーマンスについては一般的に、スペース的には少し効率がよく、時間的には、ごくわずかに効率が悪いです。パフォーマンスに関することにより、nsCOMPtr を使わないのは良くありません。このセクションを通じて提示するパターンがnsCOMPtr からより多くのものを引き出すのを手伝ってくれるでしょう。

スペース

一般的に、nsCOMPtrは、生の [XP]COM ポインタを使うよりもスペース的には、効率がよくなる可能性があります。これは主にそのデストラクタの分解とより複雑なコンストラクタと代入演算子のためです。このセクションの最適化の tips に従うことで、生のポインタで使用するのよりもオブジェクトの生成するバイトがより少ないコードを書くことができるでしょう。これらの忠告に従わないとしても、nsCOMPtr のコードは、依然として、より小さくなり、あるいは最悪でも生のポインタ版よりもごくわずか増えるだけです。詳細については、Code Bloat [長文、要約が最初にあります] を見てください。もっとも、ここでは、そのドキュメントからの推奨事項を繰り返しています。

時間

[[もっと時間パフォーマンスの測定が必要です。]]

二つ以上のサブルーチン、すなわち AddRefReleaseQueryInterface が必要な場所では、いくつかの nsCOMPtrル ーチンが分解され、そのためサブルーチンを呼び出しと対応する付加的な時間を必要とします。この時間は、特に QueryInterface により行われる作業や Release により行われるかもしれない作業にもかかわらず、極わずかです。

その他のすべての場合、nsCOMPtr は手で行われる作業をするだけです。nsCOMPtr が使用される作業の大部分は、operator-> での間接参照であり、原始的なポインタが行うものと同じです。この操作は、すべてのプラットフォームで、生の [XP]COM インタフェースポインタでの操作とちょうど同じコードを作り出し、そして同じ時間を消費します。デストラクタは、クライアントコードが生の [XP]COM インタフェースポインタに対して、Release を呼び出すのに対応しますが、処理が分解されているため、サブルーチンを呼び出すための余分な時間が必要となります。もっとも、これは、delete を実行するかもしれない Release の呼び出しと参照する方のデストラクタの両方の場合においてすでに存在するコストに対して、バランスが取れています。すべての nsCOMPtr のコンストラクタと代入演算子は、インラインです。単純なコンストラクタ、すなわち問い合わせをしないもの、は、あなたが手で書いたものと同じ作業だけを行います。AddRefReleaseQueryInterface の中の一つ以上の呼び出しを行うすべてのルーチンは、分解されており、そのため、サブルーチン呼び出しの余分なコストを含んでいます。

いくつかのルーチンが分解されているという事実により、余分なサブルーチン呼び出しのオーバーヘッドが生じます。そして、その事実および初期化がバイパスできないという事実によって、生の [XP]COM インタフェースポインタに対して、nsCOMPtr が余分な実行時間のコストを生じるのです。スペースと時間のトレードオフは、nsCOMPtr において見事にバランスが取れています。分解されたルーチンは、膨張に関する測定の直接的な結果です。

代入の好ましい構築

時間においてもスペースにおいても、nsCOMPtr への値の代入の最も効率的な方法は、構築時におけるものです。合理的である限り、代入しながら構築する方が好ましいです。nsCOMPtr のメンバをコンストラクタのメンバ初期化節で初期化すべきです。

// |nsCOMPtr| のメンバをコンストラクタの
// メンバ初期化節で初期化する...

class Bar
  {
    public:
      Bar( nsIFoo* initial_fooPtr );
      // ...
    private:
      nsCOMPtr<nsIFoo> mFooPtr;
  };

Bar::Bar( nsIFoo* initial_fooPtr )
    : mFooPtr(initial_fooPtr) // _ここで_ 初期化します
  {
    // ここではありません。
  }

付け加えておくと、代入の形式を構築の形式に変換する一時的オブジェクトを使う、最適化のパターンがあります。

// 後に代入が続くデフォルトの
// 構築は非効率的です...

nsCOMPtr<nsIFoo> foo;
nsresult rv=GetFoo(getter_AddRefs(foo));





// ...構築だけをします。

nsIFoo* temp;
nsresult rv=GetFoo(&temp);
nsCOMPtr<nsIFoo> foo=dont_AddRef(temp);

  // この「生のポインタ、getter の呼び出し、
  // |dont_AddRef| の代入」パターンを覚えて
  // ください。それは、多くの効率に関する
  // 議論で出てきたものです。

どちらの場合も、あなたは、正当な nsCOMPtr で、その値として GetFoo の結果が設定された foo というオブジェクト、および GetFoo により返された rv という状態を得ます。しかしながら、一時的変数を使う場合は、nsCOMPtr への値の設定をする構築を使っており、(ソース上では、少し複雑になっていますが)、代入に続くデフォルトの構築よりは、効率的になっています。そして、より簡単な例によって、このイベントの過程は理解されるでしょう。

代入における好ましい破壊

QueryInterface の呼び出しよりも do_QueryInterface の方が望ましいです。

繰り返し

これは、普通のポインタでデータ構造の繰り返しをする時の共通のイディオムです。例えば、

// [XP]COM オブジェクトでないものに対してポインタで繰り返しを行う...

Node* p = ...;
while ( p )
  {
    // ...
    p = p->next;
  }

同様に、このパターンが for ループとしても表現されるのをしばしば見かけます。しかしながら、これを生の [XP]COM インタフェースポインタに対して行うとどうなるか、考えてみてください。

// 生の [XP]COM インタフェースポインタで繰り返しを行います...

nsIDOMNode* p = ...;
while ( p )
  {
    // ...
    p->GetNext(&p);
      // 問題です! |p| を |Release| せずに上書きしてしまいました。
  }

おっと! p に対して、新しいポインタを設定する前に、Release し損ねてしまいました。みんながこれを多く行うため、これが通常の [XP]COM コードのリークの大きな原因となってしまいました。では、代わりにこうすることはできるのでしょうか ?

// 生の [XP]COM インタフェースポインタで繰り返しを行います...

nsIDOMNode* p = ...;
while ( p )
  {
    // ...
    NS_RELEASE(p);
    p->GetNext(&p);
      // 問題です! ダングリングしているか |NULL| であるポインタの
      // メンバ関数を呼ぼうとしています。
  }

残念ながらダメです。Release した後、nsCOMPtr は、ダングリングしている状態になるかもしれません。実は、NS_RELEASE マクロを使うと、p は、GetNext を呼び出すまでは、NULL になるでしょう。

では、同じことを nsCOMPtr で書いてあると想像してみてください。

// |nsCOMPtr| で繰り返しを行います...

nsCOMPtr<nsIDOMNode> p = ...;
while ( p )
  {
    // ...
    p->GetNext( getter_AddRefs(p) );
      // 問題です! |NULL| ポインタを通じてメンバ関数を呼び出そうとしました。
  }

ここでは、nsCOMPtr の使用は、生の [XP]COM インタフェースポインタの使用とほとんど同じです。getter_AddRefs は、 Release し、そしてそれに代入する前に p をクリアします。すなわち、GetNext が呼ばれる前にそれを行います。これは、GetNext の呼び出しを行う前に、NULL ポインタを通じて、呼び出そうとしてしまうことを意味します。生の [XP]COM インタフェースポインタと違い、nsCOMPtr は、盲目的に NULL ポインタを通じて GetNext を呼び出そうとする代わりに、assert を実行します。

これは問題です。では、解決法は、なんでしょうか。もしこれが生の [XP]COM インタフェースであれば、おそらく一時的変数を導入するでしょう。我々は、nsCOMPtrで同じことをすることができます。

// 生の [XP]COM インタフェースポインタ
// での安全な繰り返し...

nsIDOMNode* p = ...;
while ( p )
  {
    // ...

      // 一時的変数を導入することで、
      // |p| で足踏みをすることもありません。
    nsIDOMNode* temp = p;
    temp->GetNext(&p);
    NS_RELEASE(temp);
  }
// |nsCOMPtr| での安全な繰り返し...


nsCOMPtr<nsIDOMNode> p = ...;
while ( p )
  {
    // ...

      // 一時的変数を導入することで、
      // |p| で足踏みをすることもありません。
    nsCOMPtr<nsIDOMNode> temp = p;
    temp->GetNext(getter_AddRefs(p));
  }

nsCOMPtr をパラレルにするのは、容易に理解できますが、生のポインタの枠組に比べて、余分な AddRefRelease を一回ずつしなければなりません。少し変形することで、コードは見づらくなりますが、(おそらく、ごくわずかですが) より効率的になります。

// 安全で、効率的な、|nsCOMPtr| での繰り返し...

nsCOMPtr<nsIDOMNode> p = ...;
while ( p )
  {
    // ...
    nsIDOMNode* next;
    p->GetNext(&next);
    p = dont_AddRef(next);
  }

  // 見てください! これはおなじみの「生のポインタ、getterの呼び出し、
  // |dont_AddRef| の代入」パターンです。

getter を書く

コンパイラの悩みの種

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

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