- - PR -
Managed C++ != C++
投稿者 | 投稿内容 | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
投稿日時: 2002-07-16 11:59
ですからこの場合、Soundクラスが管理する(のであろう)音源リソースを、Finalizeメソッドで解放するようなコードは書いてはいけないのです。おっしゃられるとおり、Finalizeはいつ呼ばれるかわからないのですから、アンマネージリソースの解放をすべき場所ではありません。これはマルチスレッディングとは別の話です。
ありえません。GCは参照されているインスタンスを解放するようなことはありません。
一般論として、.NET Frameworkではスレッドは仮想化されています。したがって、(極論ですが)スレッドについて考慮する必要はなくなっています。少なくとも、従来の開発・実行環境に比べると、.NET Frameworkにおいてスレッディングはプログラマの必須スキルではなくなっています。
ここでは、用意したことを問題にしているのではありません。それが様々な場面で(C++的デストラクタの代替として)強調されすぎているのを問題にしています。上記のFinalizeとの兼ね合い、下記のDisposeとの兼ね合いを見ても、IDisposableパターンがC++的デストラクタの代替にはなりえないでしょう。
IDisposable::Disposeは、「リソースを明示的に破棄する」というセマンティクスを持っています。ということは、これが呼び出されたらクラスが内部で保持しているアンマネージリソースを解放しなくてはなりません。 「誰かがDisposeを呼び出すことができる」ということと、「そのクラスを複数の場所で参照している」ということは両立します。ということは、シングルトンでない限り、IDisposableを実装すると問題が出ることのほうが多いということになります。IDisposableインターフェイスと、それを取り巻くDisposableパターンには「参照カウント」のセマンティクスは含まれていないことに注意してください。 | ||||||||||||||||||||
|
投稿日時: 2002-07-16 14:18
このコードは音源リソースは一切出てきてないです。生成されたそのクラスのインスタンスをそのクラス内で、すべて管理しようとしているだけです。PlayAll()メソッドの機能を実現するためです。 ちょっと自信がないのですがまったく同じコード(つまりArrayListのマルチスレッド対策をしていない)を、C#でかくと、 public class Sound { private static ArrayList Instances; private WeakReference MyReference; static Sound(){ Instances=new ArrayList(); } public Sound() { MyReference = new WeakReference(this); Instances.Add(MyReference); } public ~Sound() { Instances.Remove(MyReference); } public void Play() { ... } public static void PlayAll() { foreach(WeakReference w in Instances) { ( (Sound)(w.Target) ).Play(); } } } ちなみにWeakReferenceになっているのは、ここで強い参照を持ってしまうと、永久的にインスタンスが破棄されないからです。ちなみにこのWeakReferenceが一番自信のないところなのですが。 もしこれがうまく動くなら、マルチスレッドは関係ないと思います。 また、こうではなく、こうしたほうがよい、というものがある場合、後学のために教えていただきたいです。 どちらかの場合、私の勘違い、または勉強不足ですので、申し訳ありません。
ならばなぜ、コレクション系クラスの説明にあれほどスレッドセーフかどうかの説明があるのでしょうか。ただ単純な疑問です。 ちなみに、仮想化されている、というのはわかります。
納得です。私もどう考えてもこれがC++的デストラクタの完璧な代用にはなりえないと思います。
ご説明ありがとうございます。なるほど、確かにそうですね。まず明確な生成破棄のタイミングがわかっていて、参照の数の管理も手動で必要であるというごく限られた場合にしか使用できないですね。一番実現しやすいのはシングルトンのみでの利用ですね。 | ||||||||||||||||||||
|
投稿日時: 2002-07-16 15:23
それならFinalizeは不要です。マネージヒープ上のインスタンスの管理はGCが行ってくれます。自分で最適化しても、それが本当に「最適」かどうかわかりませんし。つまり、「永久的にインスタンスが破棄されない」というのはたぶん正しいですが、マネージヒープ上のインスタンスに関する限り、気にする必要がないという風に僕は考えています。 そうはいっても、100万件のインスタンス(しかも全然使われない)がArrayListに登録されっぱなしなのは確かに気持ち悪いです。そこでDisposeということになるでしょう。なんらかのタイミングで明示的にDisposeを呼び出すコードを含めるわけです。 WeakReferenceも1つの選択肢だと思います。インスタンス(この場合はSoundのインスタンス)の生成コストが高く、そのクセに利用頻度が低いのであれば、WeakReferenceの出番だと。 ただし、生成コストが低ければ次々に作ればいいし、余りはどんどんGCされるのでほっておけばいいのでWeakReferenceは大して意味がありません。利用頻度が高い場合はWeakReferenceにしても強い参照が残ってしまいますからやはり無意味になってしまいます。
このコードは不要です。インスタンス(この場合はSoundのインスタンス)をつかんでいるのがWeakReferenceしかない場合、GCはインスタンスを解放します。WeakReference自体には大してコストはかかりません。
たしかにおっしゃるとおりです。ま、v1だし。。。ってダメですか? ちなみにWeakReferenceですが、
これだと例外になる可能性があります。
という感じで触ったほうがいいと思います。 | ||||||||||||||||||||
|
投稿日時: 2002-07-22 04:04
申し訳ありません、週末忙しくて返信が遅くなってしまいました。
ありがとうございます。確かにそうだと思います。いや、生成解放のきちっとしたプログラムをしていたので気づくのが遅れた、といった感じでして。少し目からうろこです。そもそも、PlayAllでは、どの音が鳴るのか、というぐらいはプログラマが管理すべきだと思いますので、Disposeパターンを使用したほうが良いような気もしますし。そのタイミングでこのリストからの消去を行うべきかも知れません。
そうですね。あれから少し考えてみたのですが、WeakReferenceを使う場合は、やはりリストをクリアする部分を入れた方が良いような気もします。メモリリークが(この場合nullになったWeakReference)いつまでもリストに残ったままだと、気持ち悪い以前に、生成破棄を繰り返し行っていくと、このリストが肥大化していってしまいます。 それが一番メモリリークで怖いこと(つまりアプリケーションを長い間使っていると、段々と使用領域が増えて、メモリを圧迫してくる)ですし。PlayAllの速度もじわじわと遅くなっていきますね。 考えた一つの案としては、コンストラクタでそのチェックを行うようにするって事でしょうか。ある条件で、WeakReferenceが切れているものをリストから削除するようにすればいいなぁと。 とりあえず、マルチスレッドが問題となるようなコード(つまり、コレクション系クラスの操作など)は、Finalizeには書かない、という方向がいいのかなと。 ちなみにマルチスレッドなんですが、lockとかありますし、完全に考えから駆逐するのは、無理のような気もします。C#はとても扱いやすくなっているので問題にはならないとは思いますが。 | ||||||||||||||||||||
|
投稿日時: 2002-07-23 09:02
やっぱり、どう見ても、managed C++ != C++、少なくとも、標準C++からの拡張とは
思えません。 デストラクタ。メモリリソースの取得解放をmanageするのですから、「明示的に呼ば なくてもよい」のはわかりますが、MS用意のクラスについては明示的に呼んではいけない というのが理解できません。C++の標準的なコーディングからはずれると思います。 また、デストラクタが勝手にFinalizeに変更されますが、これの実装方法が?です。と いうのも、クラス宣言の中で実装すると、 class A { public: ~A(){}; }; で実装できるのに、cppファイルに記述する時は void Finalize(void){} でないとリンクでエラーになる「ことがある」のです。これは開発環境も絡んできます けど。小一時間悩みました。 データベースアクセスでOleDbConnectionやOleDbCommand、OleDbReaderなどを 使いますが、これらを隠蔽するクラスを作ることにしました。 class DBAccess { private: OleDbConnection *connect; ArrayList *data; public: DBAccess(){ connect = new OleDbConnection(/*接続文字列*/); connect->open(); }; StoreMemory(String *sql){ OleDbCommand *cmd = new OleDbCommand(sql, connect); OleDbReader *rd = cmd->ExecuteQuery(); // dataに放り込む cmd->Close(); }; ~DBAccess(){ connect->Close(); }; }; とすると、実行時にDBAccessのデストラクタでconnectの実態がないというエラーに なります。おかしいなぁ??今までこういう書き方でいけてたのになぁ、と3時間ほ ど悩んで、「デストラクタは別のスレッドで実行される」ことを思い出しました。と いうことは、DBAccessのインスタンスより、それが持っているOleDbConnectionの インスタンスが先に解放されてしまっている(可能性がある)ということですか?! 開発者が意図しないうちに解放されるおそれがあるということは、解放しておいてほ しいのに解放してくれない、ということもあり得ますね!?(明示的にデストラクタ が呼べないクラスもあるのですから、当然ですね。Finalizeってprotectedメンバなの に、外から呼べてしまえるのはコンパイラのバグじゃないですか???) どうも、デストラクタ周りの使い方がなじめません。gcrootでラップする、というのは わかりましたが、1から作るシステムじゃぁ、はじめから__gcの仕様にあわせる方が、 メンテナンスを考えると、いいでしょう。アンマネージだとVBから参照できないし…。 「移行期」ゆえでしょうが、どうも使いにくい。 | ||||||||||||||||||||
|
投稿日時: 2002-07-23 10:32
複数スレッドから同一のコレクションにアクセスするような場合に、スレッドセーフでないとデータの異常が出る場合があるからでは? もっとも、私は.NETを学び始めたばかりなので、具体的にどのような場合がそうなのかはわかりません。今の所スレッドセーフかどうかを気にするような場面には遭遇してません。 (はずしていたらごめんなさい) | ||||||||||||||||||||
|
投稿日時: 2002-07-23 12:11
いいえ。それはありえません。それから、GCとマルチスレッディングは無関係です。GCを実行しているスレッドと自分のスレッドとでレースコンディションは発生しません。
「解放しておいてほしい」ということを表現する手段がありませんので、そのようなこともありえません(Finalize/Disposeではマネージヒープは解放できない)。
Finalizeは外からは呼べません。Protectedメンバーですから。MC++でdeleteを発行すると、cl.exeはそれを__dtorというメソッドへの呼び出しとしてCILを出力します。__dtorメソッドの可視性は、「デストラクタ」の可視性に依存します。このメソッドの本体は自動作成され、内部でFinalizeメソッドを呼び出しています。delete pa;という呼び出しはFinalizeを呼んでいるわけではありません。 | ||||||||||||||||||||
|
投稿日時: 2002-07-23 12:13
[quote]
また、デストラクタが勝手にFinalizeに変更されますが、これの実装方法が?です。と いうのも、クラス宣言の中で実装すると、 class A { public: ~A(){}; }; で実装できるのに、cppファイルに記述する時は void Finalize(void){} でないとリンクでエラーになる「ことがある」のです。これは開発環境も絡んできます けど。小一時間悩みました。 [/qoute] デストラクタの定義部に戻り値(void)を書いているからではないのですか?あるいは引数にもvoidを書いているか。もし書いていないのなら、勘違いです。申し訳ありません。
もちろんそうです。ただ、ここではそのことについていっているのではなくて…「C#、というか.NETではスレッドが仮想化されてマルチスレッドを考える必要がない」とNothingBut.NETFXさんがおっしゃられたので、ではなぜ…と返しただけです。 ただ、NothingBut.NETFXさんも、極論とおっしゃっているのでわかっていらっしゃると思うのですが、マルチスレッドについては考える必要がある部分もあります。というか、それが簡単になっている、というか初めからC#はマルチスレッドに付いて考えていてわかりやすくコーディングできるようになっているので、あまり考える必要がないのではないか?という意味だと私はとりました。 コレクションをすべてスレッドセーフにする事も可能(すべてのメソッドについて、スレッドをロックすればいい)なのですが、パフォーマンス上の理由や、汎用性の部分から現在のコレクションの実装はこのようになっていると思われます。当然スレッドセーフでない場合があればリファレンスに記述するべきでしょう。 個人的には、スレッドの仮想化、というのは、マルチスレッドを考えなくてもよいようにするのではなく、最適化や、「シングルスレッドしか許されない環境」のことを考慮してやっているのではないのかなぁって気がしますが。 |