nsCOMPtrはリークを防ぐのを助けるツールです。
nsCOMPtrはスマートポインタです。これは、文法的にはCやC++の普通のポインタと同様に振る舞うテンプレートクラスです。すなわち、*や->でそれが指すものを取得できます。nsCOMPtrは、生の[XP]COMインターフェースへのポインタと違い、AddRef、Release、QueryInterfaceをあなたの代わりに管理してくれるという点で賢いです。nsCOMPtrは以下のソースファイルで定義されています。[#]:
...でもあなたはまだその中を見たいとは思わないでしょうが。
nsCOMPtrを使えば、あなたは生の[XP]COMのインターフェースポインタを使ったものより、より短く、より綺麗な、より明確なコードを書くことができます。
ここでは[XP]COMの基本的な事柄をざっとなめます。あなたはこれらを既に知っているべきで、この短いセクションをざっと眺めることができるべきです。もしこれらのことに馴染みがなければ、あなたはまだnsCOMPtrへの準備ができていません。COMの背後にある基本的ルールと論拠を学ぶよい場所としては、Don Box[#]によるEssential COM[#]があります。Don BoxはEffective COM[#]で、COMの更なる詳細、罠、落とし穴を説明しています。またあなたは、適度なC++の知識を持っていなくてはなりません。多分この分野に関する3つの最も役に立つ本は、Bjarne StroustrupのThe C++ Programming Language[#]、Effective C++[#]、Scott MeyersのMore Effective C++[#]でしょう。
全ての[XP]COMオブジェクトはヒープにアロケートされます。クライアントはこれらのどのオブジェクトの実装に関しても多くを知ることはできません。クライアントはインターフェースへのポインタを通してこれらを参照するだけです。つまり、スタティックなタイプのポインタは抽象基底クラスへのポインタで、ポインタで指される実際のオブジェクトはその抽象基底クラスから派生したクラスです。[XP]COMオブジェクトは"インターフェースを実装する"と表現されます。クライアントの持つオブジェクトへの参照は、典型的には"インターフェースポインタ"と呼ばれます。
1つのオブジェクトは複数のインターフェースを実装しうるのです。各々のインターフェースは(少なくとも概念的には)別々に"参照カウンティング"されます。すなわち、インターフェースはそれへの参照を保持するクライアントの数のカウントを保持します。そのカウントが0になるとインターフェースは自分自身を破棄するかもしれません。クライアントは、インターフェースへの参照を獲得した時、参照カウントをインクリメントし、それを解放する時、参照カウントをデクリメントすることにより、この参照カウントを正確に保つことが期待されます。これらを行なう為に、全てのインターフェースは、AddRefとReleaseのメンバ関数を提供する抽象基底クラスを継承します。
[XP]COMのルールは、インターフェースポインタを作成したり返したりする関数は必ずそれをAddRefする、というものです。これにより呼び出し側は、無限に参照を保持できるようになり、必要がなくなればReleaseをコールします。インターフェースへの最後のポインタがリリースされると、インターフェース(そして結果的に、典型的にその基となるオブジェクト)は自分自身を破棄するでしょう。インターフェースに対し明示的なAddRefが存在する限り、それは存在し続けます。もしあなたがReleaseをコールするのを忘れると、オブジェクトがリークし、すなわち、オブジェクトのメモリは再使用できなくなります。リークはよくありません。:−)
それを介してAddRefとReleaseをコールするところの参照は、所有参照と呼ばれます。それは、基礎となるオブジェクトに対し、杭をうちます。そのオブジェクトは、所有参照が放棄されるまで解放されません。全ての参照が所有参照である必要はありません。実際、もし2つのオブジェクトがどうにかして(一時的にでも)お互いに所有し合ってしまった場合、所有権のサイクルを壊す掟破りのメカニズムでも加えない限り、どちらのオブジェクトも再使用が難しくなります。Some COM Ownership Guidelines[#]が、いつ所有参照が必要になるかの幾つかのヒントを提供します。以下のリストはよい出発点ですが、完璧ではありません。
以下の時、所有参照を使う:
以下の時、所有参照は必要ない:
これらにより、参照カウンティングをプログラマが手動で正しく保つのは大変だ、ということが分ります。それは単純に見えますが、実際には適切な時にReleaseするのを忘れたり、多くAddRefしたり、少なくAddRefしたりすることは、いとも簡単に起こります。
nsCOMPtrはAddRef、Releaseや他の危険なことを、あなたに代わって管理します。nsCOMPtrは生の[XP]COMインターフェースポインタと同じように見え、同じようにC++に対して振るまいます。しかしそれは、自分が指すオブジェクトを所有していることを知っています。あなたに少しばかりの慣れを要求しますが、結果的により少ないコード量、より明確で安全なコード、より少ないリークとなります。
実例として、(最もコンパクトな形で)典型的なちょっとしたコードを示します。ここでは、[XP]COMインターフェースポインタをメンバ変数に設定しています。つまり、セッター関数の主要処理です。生の[XP]COMインターフェースポインタと、nsCOMPtrを、並べてしめします。
// raw [XP]COM interface pointers... // given: |nsIFoo* mFooPtr;| /* |AddRef| the new value if it's not |NULL|; assign it in; and |Release| the old value, if any (so we don't leak it). This order of assignment is special and must be used to avoid particular ownership bugs. */ NS_IF_ADDREF(aFooPtr); nsIFoo* temp = mFooPtr; mFooPtr = aFooPtr; NS_IF_RELEASE(temp); |
// |nsCOMPtr|... // given: |nsCOMPtr<nsIFoo> mFooPtr;| /* This assignment automatically |Release|s the old value in |mFooPtr|, if any, and |AddRef|s the new one, in the appropriate sequence to avoid the ownership bug mentioned earlier. */ mFooPtr = aFooPtr; |
付け加えると、生の[XP]COMインターフェースポインタを使用するクラスは、mFooPtrをReleaseするデストラクタと、mFooPtrをNULL(かまたは意味のある値)で初期化するコンストラクタが必要になります。
nsCOMPtrは、あなたが、生の[XP]COMインターフェースポインタで書くより、リークに強く、例外に対し安全で、だらだらとしないコードを書くのを助けます。nsCOMPtrを使えば、AddRef、Release、QueryInterfaceを手動で呼ぶ必要は全くないかもしれません。
それでもあなたは[XP]COMを理解しなければなりません。どの関数がAddRefされたインターフェースポインタを返し、どの関数がそうでないかを知らねばなりません。プログラムロジックが循環参照によるゴミをださないようにしなければなりません。nsCOMPtrは万能薬ではありません。しかしそれは、有用で、使い易く、よくテストされ、礼儀正しいです。それは、関数の作者があなたと協調することを必要としないし、あなたがそれを使用することで他の人にもその使用を強制することもありません。
ほとんどの場合、あなたは生[XP]COMインターフェースポインタを使うのと全く同じ方法で、nsCOMPtrを使うでしょう。宣言の仕方のちょっとした違いに注意してください。
// raw [XP]COM interface pointers... nsIFoo* fooPtr = 0; // ... fooPtr->SomeFunction(x, y, z); AnotherFunction(fooPtr); if ( fooPtr ) // ... if ( fooPtr == foo2Ptr ) // ... |
// |nsCOMPtr|... nsCOMPtr<nsIFoo> fooPtr; // ... fooPtr->SomeFunction(x, y, z); AnotherFunction(fooPtr); if ( fooPtr ) // ... if ( fooPtr == foo2Ptr ) // ... |
2つの主要な違いがあります。1つ目:AddRefやReleaseを呼ぶ必要はないし、呼ぶことが許されてもいない
// raw [XP]COM interface pointers... // given: |nsIFoo* mFooPtr;| /* Note: this sequence is not the correct order to do assign raw pointers anyway (see Comparison 1) but I need it for this comparison. */ NS_IF_RELEASE(mFooPtr); mFooPtr = aFooPtr; NS_IF_ADDREF(mFooPtr); |
// |nsCOMPtr|... // given: |nsCOMPtr<nsIFoo> mFooPtr;| /* You no longer need, nor will the compiler let you, call |AddRef|, or |Release|. */ NS_IF_RELEASE(mFooPtr); // Error: |Release| is private mFooPtr = aFooPtr; NS_IF_ADDREF(mFooPtr); // Error: |AddRef| is private |
2つ目:生[XP]COMインターフェースポインタ引き数を介して結果を返すゲッターにnsCOMPtrへのアドレスを渡すことはできません。getter_AddRefs指示子でnsCOMPtrを引用しなければなりません。
// raw [XP]COM interface pointers... nsIFoo* foo; GetFoo(&foo); |
// |nsCOMPtr|s... nsCOMPtr<nsIFoo> foo; GetFoo(getter_AddRefs(foo)); |
こうです。あなたはnsCOMPtrを使い始めるのに十分なことを知りました。もっと複雑な状況でnsCOMPtrを使う時、あなたが知りたくなる少しの他の詳細もありますが、あなたが今学んだことは使用方法の90%をカバーするでしょう。我々の使用するコンパイラの幾つかのバグに起因する、幾つかの思いがけない違いがあります。他の人々のビルドを壊さない為に、これらのことは避けたいでしょう。これらの違いに関する不幸な真実は、それらはあなたのプラットフォームでは多分コンパイルできるということです。全ての場所でコンパイルできればよいのですが、そうではないので、気を付けましょう。
// Things some of our compilers hate... nsIFoo* rawFooPtr; nsCOMPtr<nsIFoo> foo; // ... // Don't start a comparison with |0|, // or |NS_NULL|... if ( NS_NULL != foo ) // ... // Similarly, |==|. // Avoid comparing an |nsCOMPtr| to // a raw [XP]COM interface pointer if ( foo == rawFooPtr ) // ... // Similarly, |!=|, or re-ordering |
// ...so do it this way, instead. nsIFoo* rawFooPtr; nsCOMPtr<nsIFoo> foo; // ... // ...prefer the implicit test if ( foo ) // ... if ( !foo ) // ... // ...prefer two raw pointers if ( foo.get() == rawFooPtr ) // ... |
nsCOMPtrから多くを引き出すのに役立つ幾つかの更なる事柄があります。
非常にしばしば、最初に、QueryInterfaceをコールすることによりインターフェースポインタを取得します。QueryInterfaceは他と同様にゲッターで、あなたはそれをコールする1つの方法を既に知っています。上で示したように、getter_AddRefsルールを適用するのです。
// A way (though not the best way) to |QueryInterface| into an |nsCOMPtr|... nsCOMPtr<nsIFoo> foo; nsresult rv = bar->QueryInterface(NS_GET_IID(nsIFoo), getter_AddRefs(foo)); // Or, if you're a savvy [XP]COM programmer, // you use the type-safe version... nsresult rv = CallQueryInterface(bar, getter_AddRefs(foo)); |
QueryInterfaceはとても頻繁に使われますが、nsCOMPtrはそれをコールする特別な仕掛けを持っています。この仕掛けはタイプセーフで、QueryInterfaceの結果からnsCOMPtrが直接構築されることを可能にします。正しい値からの直接構築は、構築してから設定するより、効率的です。この仕掛けとはdo_QueryInterface指示子です。do_QueryInterfaceを使うことにより、上のサンプルは以下のようになります。
// The best way to |QueryInterface| into an |nsCOMPtr|... nsresult rv; nsCOMPtr<nsIFoo> foo( do_QueryInterface(bar, &rv) ); // Or, if you don't care about the |nsresult| nsCOMPtr<nsIFoo> foo( do_QueryInterface(bar) ); |
nsCOMPtrは嬉しいことにAddRefとReleaseを暗黙のうちにコールしてくれます。このことはQueryInterfaceには適応されません。nsCOMPtrは、あなたがdo_QueryInterface指示子の形で明示的に許可しない限り、それへの代入に於いてはQueryInterfaceを行ないません。隠れたQueryを心配する必要はありません。しかし、Queryすべきなのにしなかった、ということには気を付けてください。例えば、生ポインタを代入することです。C++は代入を許しますが、[XP]COMでは許されないことです。nsCOMPtrは実行時にアサートするでしょう。ポインタを違う型の[XP]COMインターフェースに代入する時はいつでも、do_QueryInterfaceを使ってください。その型がたまたまnsCOMPtrのベースタイプから派生したものであってもです。
class nsIBar
: public nsIFoo ... { ... };
nsIBar* p = ...;
// C++ thinks every |nsIBar*| is an
// |nsIFoo*|, therefore, C++ allows
// this...
nsCOMPtr<nsIFoo> foo = p;
// ...even though it is an [XP]COM
// type error
|
class nsIBar
: public nsIFoo ... { ... };
nsIBar* p = ...;
// No type error here...
nsCOMPtr<nsIFoo> foo( do_QueryInterface(p) );
|
C++のタイプシステムと[XP]COMのタイプシステムとは実際には2つの独立した事柄であることを覚えておいてください。[XP]COMインターフェースは抽象的なC++のベースクラスとして表現されるので、あなたはC++にその違いを操作させたり、インターフェースの型の間を辿るのにC++のキャストを使ったりしたくなるかもしれません。これは間違いです。[XP]COM型の間を辿るのに是認された唯一の方法は、QueryInterfaceを使うことです。上の例では、C++がpから引き出したnsIFoo*が、p->QueryInterface()が返すものと同一だと過程する何の理由もありません。
dont_AddRefは、既にAddRefされたポインタを代入するのに役立つ、同じような指示子です。例えば、関数のリターン値としてポインタを返すゲッターをコールした時などです。
nsCOMPtr<nsIFoo> foo( dont_AddRef(CreateFoo()) ); // |CreateFoo| |AddRef|s its result, as all good getters do |
nsCOMPtrは、所有参照として振る舞うのに必要な全てのことを行ないます。しかし、nsCOMPtrは他の所有ポインタを作ることには協力しません。nsCOMPtrが代入される時に自動的にポインタをAddRefsすることを学んだ後、それが参照される時にも同じことをすると思うのは自然な発想かもしれません。この間違った概念をデモする為の小さなコード例を示します。
// Incorrect assumptions about |nsCOMPtr|... nsresult nsCacheRecord::GetFileSpec( nsIFileSpec** aFileSpecResult ) /* ...fills in the callers |nsFileSpec*| (which the caller supplied the address of) with a copy of my member variable |mFileSpec|, an |nsCOMPtr|. I.e., this function is a `getter'. Remember: good [XP]COM getters always |AddRef| their result. */ { // ... *aFileSpec = mFileSpec; // the |nsCOMPtr| should take care of the refcount here, right? return NS_OK; } |
明らかに、このコードの著者は(多分少し疑問に思いながらも)nsCOMPtrのmFileSpecは、それが*aFileSpecに代入される時、自動的にAddRefするだろうと信じています。そうはなりません。nsCOMPtrは自分の為だけに自動的にAddRefとReleaseをコールします。他の全ての状況に於いては、それは生[XP]COMポインタのただの代価としてデザインされています。生ポインタが必要な状況においてnsCOMPtrが使われる場合はいつでも、nsCOMPtrは自動的にそれを提供します。
// |nsCOMPtr| produces a raw pointer when needed... nsCOMPtr<nsIFoo> foo = ...; // 1. Assigning into a raw pointer nsIFoo* raw_foo = foo; // 2. Assigning into another |nsCOMPtr| nsCOMPtr<nsIFoo> foo2 = foo; // 3. As a parameter SetFoo(foo); // 4. Testing the value in an |if| expression // 5. Calling a member function if ( foo ) foo->DoSomething(); |
これらの全てのケースで、全く同じコードが実行されます。(ケース2は微妙に違いますが、意図は同じです。)各々のケースで、あなたは結局自分の目的の為に生ポインタを抽出します。もしnsCOMPtrが、あなたがこれを行なう度に値をAddRefしたとしたら、ケース4と5では明らかにいつもリークを起こすでしょう。ケース3のSetFooは、nsCOMPtrを渡されたらそれが既にAddRefされていることを知り、生ポインタを渡されたらAddRefされていないことを知るので、2つの違った方法で書かれなければならないでしょう。実際、矛盾はこれらよりもっと深くまで広がります。これらの全てのケースは、もし出力に対して自動的にAddRefしたならば、クライアントの視点から見て、nsCOMPtrと生ポインタが違った動きをすることになることを示しています。ゴールは、nsCOMPtrがそれらの代わりとなるように、それらが同じように動くようにすることです。(自分の所有権を管理するモジュールです)
あなたが今知ったことから、ルールは明らかです。上で示されたように、そしてあなたが異を唱えないなら、nsCOMPtrは、代入されるときAddRefします。参照されるときは何もしません。
所有参照としてインターフェースポインタを使う場所ではどこでもnsCOMPtrを使うべきです。例えば、それに対してAddRefとReleaseをする時です。メンバ変数としてnsCOMPtrを使うべきです。それは単なるセッターであり、コンストラクタ、デストラクタ、代入オペレータを排除します。スタックでnsCOMPtrを使うべきです。QueryInterfaceのコールを快適にし、エラー処理の為の複雑なロジックを排除します。
所有参照が必要でないところでは、nsCOMPtrは使わないでください。Some COM Ownership Guidelines[#]を参照してください。[XP]COMインターフェース以外の何かを指すポインタには、nsCOMPtrは使わないでください。[XP]COMインターフェースの内部や、他の人々にその使用を強制するようなやり方で、nsCOMPtrを使わないでください。プレーンな古いCコードのなかでは使わないでください。nsCOMPtrはもちろんC++のみの構造です。nsCOMPtrを決してキャストしないでください。それをすると、ほとんどリークが保証されたようなものです。
一般的に、XPCOM(つまり、スクリプタブル)関数の識別子内で、nsCOMPtrを使いたいとは思わないでしょう。nsCOMPtrは現在IDLにより直接サポートはされていません。しかし、あなたは時々スクリプタブルでない関数内でnsCOMPtrを使いたくなるかもしれません。
この習慣は危険です。AddRefされたポインタを関数のリターン値として返すことは、ほとんどどの様な形で行なっても、リークや無効なポインタなどの、かなりひどい潜在的エラーに行きつきます。nsCOMPtrをリターンすることは(クライアントがそれに所有権を与えたことをクライアントに教えるので)よい考えのように見えますが、これは無効なポインタを引き起こします。以下のコードを考えてみてください:
// Don't return |nsCOMPtr|s... nsCOMPtr<nsIFoo> CreateFoo(); // ... nsIFoo* myFoo = CreateFoo(); // Oops: |myFoo| now dangles! // |CreateFoo| returns an |nsCOMPtr|, which // automatically |Release|s right after this // assignment. Now |myFoo| refers to a // deleted object. |
already_AddRefed<T>をリターンすることにより、呼び出し側に、この危険なしにそれらに所有権を与えたことを通知できます(バグ#59212参照)。nsCOMPtrは、already_AddRefedされた値は、AddRefすべきではない事を知るようになります。
// Preferred form: if you must return a pointer, use |already_AddRefed|... already_AddRefed<nsIFoo> CreateFoo(); // ... nsIFoo* myFoo1 = CreateFoo(); // doesn't dangle nsCOMPtr<nsIFoo> myFoo2( CreateFoo() ); // doesn't leak nsCOMPtr<nsIFoo> myFoo3( dont_AddRef(CreateFoo()) ); // redundant, but legal and correct |
これを、既にAddRefした生ポインタをリターンすることを原因とする、最も頻繁に起こりうるリークと比べてみて下さい:
// Don't return raw pointers; that incites leaks... nsIFoo* CreateFoo(); // returns an |AddRef|ed pointer // ... nsCOMPtr<nsIFoo> myFoo = CreateFoo(); // Oops: leak; nsCOMPtr<nsIFoo> myFoo( dont_AddRef(CreateFoo()) ); // Since |CreateFoo| already |AddRef|s its result, we must remind // our |nsCOMPtr| not to. It's easy to forget. Prevent it in advance // by not returning pointers as function results, or else by returning // an |already_AddRefed<T>| as above. |
この習慣は役に立たないどころか、実害があります。引き数は関数コールと同じ生存期間を保証されるので、引き数をAddRefする必要はありません。関数コールを超えて生き残る構造体のメンバに値を格納する時のみ、AddRefが必要になります。これは、関数の引き数ではなく、構造体の適切なメンバがnsCOMPtrであるべきことを意味します。更にこの書き方は、呼び出し側に、単に関数をコールする為にnsCOMPtrが必要なのではないかと思わせ、混乱させます。
上の書き方と全く同じで、この習慣は役に立たないどころか、実害があります。もし呼び出し側が生ポインタを渡した場合には、nsCOMPtrを値渡しするのと同じ良く無いことが起こります。
この習慣は、呼び出し側に、それがnsCOMPtrを使用する事と、ちょっとした余分な仕事を要求します。と言うのは、nsCOMPtrのoperator&は(キャストによるリークを防ぐ為に;バグ#59414参照)privateだからです。この方法は、入出力引き数として宣言する事により、以下の様に可能ですが、nsCOMPtrを参照渡しする方が好ましいでしょう。
// Passing an |nsCOMPtr| by pointer requires extra work... void f( nsCOMPtr<nsIFoo>* ); // ... nsCOMPtr<nsIFoo> myFoo = ...; f( address_of(myFoo) ); |
これは入出力引き数を提供する為に好ましい方法です。もし代りに生ポインタを使った場合、関数内部では、入力値として呼び出し側がどの所有関係を持っているかが、分らなくなります。結果として、新しい値を代入する前にReleaseすべきかどうかが分らなくなります。引き数をnsCOMPtr&として宣言する事により、関係が明確になります。
nsCOMPtrは、所有参照です。それが指すものはなんであれAddRefされ、nsCOMPtrをその所有者としてカウントします。nsCOMPtrは、別のオブジェクトを指せるようにする為や、スコープの外に出た時に、解放される前に必ずReleaseが呼ばれます。nsCOMPtrは、新しい値がnsCOMPtrに代入される時はいつでも、もし古い参照があればこれを常にReleaseし、(あなたが既にAddRefしている事を通知しない限り)新しい方をAddRefします。
ほとんど全てのケースでは、生[XP]COMインターフェースポインタを使うのと同じやり方で、nsCOMPtrを使います。(でも、Comparison 5のコンパイラ問題に注意)それに対しAddRefやReleaseを明示的に呼ぶ必要はなく、コンパイラはそれを許しません。変更なしにnsCOMPtrを使えない唯一の場所は、生[XP]COMインターフェースポインタが出力引き数の時です。この場合には、nsCOMPtrをgetter_AddRefsで囲みます。[omparison 4参照]
nsCOMPtrに値を代入する時には、(それが生[XP]COMインターフェースポインタでもnsCOMPtrでも)たいがい、追加の指示子なしに、単に他のポインタを渡すだけです。[Comparison 1のnsCOMPtr側を参照]上記の様に、何の指示子もなしに、nsCOMPtrは古い参照があればこれをReleaseし、新しいのをAddRefします。これは、代入しようとしているものがまだ新しい参照の為にAddRefされていない場合には、適切なことです。これは、関数コールで取得したものではない値をポインタに代入する場合の典型的なケースです。例えば、引き数で受け取った場合や、構造体から引っ張り出した場合です。
新しい値をdont_AddRefで囲むことにより、nsCOMPtrに、代入の時新しい値にAddRefする必要がないことを通知できます。例えば、あなたの代りに既にAddRefをコールしている関数(全ての正しい[XP]COMゲッター)から新値を受け取った時に、これをしてください。
ポインタに異なったインターフェース型を代入すべきではありません。最初に新しい型でQueryしなければなりません。[Comparison 6とその前後参照]nsCOMPtrが暗黙の内にQueryInterfaceを呼ぶことはありません。すなわち、あなたは自分でそれを呼ばねばならないか、nsCOMPtrにdo_QueryInterfaceで明示的に要求しなければならないかの、どちらかです。do_QueryInterface指示子は、代入の過程でQueryを行なうことを可能にします。このより良い機構は、nsCOMPtrを構築し、後に正しい値を代入するのではなく、正しい値から直接nsCOMPtrを構築します。構築に続いて代入するより、構築だけで済ませる方が効率的です。可能なときはいつでも、代入より構築の方が好ましいでしょう。AddRefされたポインタをリターン値として返す関数に、do_QueryInterfaceを適用しないように注意してください。[この短いセクションを参照]
更なる詳細は、リファレンスマニュアルに続く。
|
Copyright © 1998-2001 The Mozilla Organization.
Last modified November 12, 2000. Document History. |