このセクションは、あなたが、すでに 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 として正しい nsISupports
に QueryInterface
することを強制するのであれば、煩わしいかもしれません。
NULL
間接参照の保護手段
もし中が空の時に間接参照しようとすると、nsCOMPtr
は、実行時にアサートします。例えば、
nsCOMPtr<nsIFoo> foo; // 注: デフォルトでは、|0| に初期化されます。 foo->DoSomething(); // NS_PRECONDITION: "You can't dereference a NULL nsCOMPtr with operator->()" |
同様の事前条件が operator*
のために、介在します。
参照カウントの保護手段
nsCOMPtr
から元の生のポインタを取り出すすべての操作に対して、安全な特徴を実装するための C の別のトリックを使います。返ってきたポインタに対して、AddRef
、Release
、delete
を実行することができません。
nsCOMPtr<nsIFoo> foo = ...; foo->AddRef(); // エラー: |AddRef| はプライベートです。 delete foo.get(); // エラー: |operator delete| はプライベートです。 NS_RELEASE(foo); // エラー: |Release| はプライベートです。 |
もちろん、nsCOMPtr
によって提供される安全性に関する最も重要な特徴は、それが適切な時期に自動的に AddRef
と Release
を実行することです。
キャスト
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
で初期化されます。ちょうどプリミティブなポインタのように、値が0
のnsCOMPtr
は、どのオブジェクトも指しません。そして、if (foo)
や if (!foo)
のように式をテストすることができます。
上述した指示は、この表によりもっとはっきりするでしょう。
QueryInterface しない | QueryInterface する | |
AddRef する
|
| |
AddRef しない
|
例えば、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 は nsCOMPtr
にAddRef
を実行した新しい値を設定します。我々は、これらの状況で、生の [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 インタフェースポインタに対する実用的な置き換えとなるべく調整されています。所有する参照として使うのであれば、どの場所においてもです。nsCOMPtr
s のパフォーマンスについては一般的に、スペース的には少し効率がよく、時間的には、ごくわずかに効率が悪いです。パフォーマンスに関することにより、nsCOMPtr
を使わないのは良くありません。このセクションを通じて提示するパターンがnsCOMPtr
からより多くのものを引き出すのを手伝ってくれるでしょう。
スペース
一般的に、nsCOMPtr
は、生の [XP]COM ポインタを使うよりもスペース的には、効率がよくなる可能性があります。これは主にそのデストラクタの分解とより複雑なコンストラクタと代入演算子のためです。このセクションの最適化の tips に従うことで、生のポインタで使用するのよりもオブジェクトの生成するバイトがより少ないコードを書くことができるでしょう。これらの忠告に従わないとしても、nsCOMPtr
のコードは、依然として、より小さくなり、あるいは最悪でも生のポインタ版よりもごくわずか増えるだけです。詳細については、Code Bloat [長文、要約が最初にあります] を見てください。もっとも、ここでは、そのドキュメントからの推奨事項を繰り返しています。
時間
[[もっと時間パフォーマンスの測定が必要です。]]
二つ以上のサブルーチン、すなわち AddRef
、Release
、QueryInterface
が必要な場所では、いくつかの nsCOMPtr
ル ーチンが分解され、そのためサブルーチンを呼び出しと対応する付加的な時間を必要とします。この時間は、特に QueryInterface
により行われる作業や Release
により行われるかもしれない作業にもかかわらず、極わずかです。
その他のすべての場合、nsCOMPtr
は手で行われる作業をするだけです。nsCOMPtr
が使用される作業の大部分は、operator->
での間接参照であり、原始的なポインタが行うものと同じです。この操作は、すべてのプラットフォームで、生の [XP]COM インタフェースポインタでの操作とちょうど同じコードを作り出し、そして同じ時間を消費します。デストラクタは、クライアントコードが生の [XP]COM インタフェースポインタに対して、Release
を呼び出すのに対応しますが、処理が分解されているため、サブルーチンを呼び出すための余分な時間が必要となります。もっとも、これは、delete
を実行するかもしれない Release
の呼び出しと参照する方のデストラクタの両方の場合においてすでに存在するコストに対して、バランスが取れています。すべての nsCOMPtr
のコンストラクタと代入演算子は、インラインです。単純なコンストラクタ、すなわち問い合わせをしないもの、は、あなたが手で書いたものと同じ作業だけを行います。AddRef
、Release
、QueryInterface
の中の一つ以上の呼び出しを行うすべてのルーチンは、分解されており、そのため、サブルーチン呼び出しの余分なコストを含んでいます。
いくつかのルーチンが分解されているという事実により、余分なサブルーチン呼び出しのオーバーヘッドが生じます。そして、その事実および初期化がバイパスできないという事実によって、生の [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
をパラレルにするのは、容易に理解できますが、生のポインタの枠組に比べて、余分な AddRef
と Release
を一回ずつしなければなりません。少し変形することで、コードは見づらくなりますが、(おそらく、ごくわずかですが) より効率的になります。
// 安全で、効率的な、|nsCOMPtr| での繰り返し... nsCOMPtr<nsIDOMNode> p = ...; while ( p ) { // ... nsIDOMNode* next; p->GetNext(&next); p = dont_AddRef(next); } // 見てください! これはおなじみの「生のポインタ、getterの呼び出し、 // |dont_AddRef| の代入」パターンです。 |