4

私が取り組んでいる C++/CLI (および C#) プロジェクトの早い段階で呼び出されているように見えるファイナライザーに問題があります。これは非常に複雑な問題のようで、コードから多くの異なるクラスと型について言及します。幸いなことに、これはオープン ソースであり、次のリンクをたどることができます: Pstsdk.Net (mercurial リポジトリ) また、必要に応じてファイル ブラウザーに直接リンクすることも試みたので、読みながらコードを表示できます。扱うコードのほとんどはpstsdk.mcpp、リポジトリのフォルダーにあります。

現在、コードはかなりひどい状態にあり (私はそれに取り組んでいます)、私が取り組んでいるコードの現在のバージョンはFinalization fixes (UNSTABLE!)ブランチにあります。そのブランチには 2 つの変更セットがあり、私の長々とした質問を理解するには、両方に対処する必要があります。(変更セット: ee6a002df36fおよびa12e9f5ea9fe )

背景として、このプロジェクトは C++ で書かれたアンマネージ ライブラリの C++/CLI ラッパーです。私はプロジェクトのコーディネーターではありません。また、コードを見た多くの人が同意するであろうと確信しているように、私が同意しない設計上の決定がいくつかありますが、余談になります。元のライブラリのレイヤーの多くを C++/CLI dll にラップしていますが、使いやすい API は C# dll で公開しています。これは、プロジェクトの目的がライブラリ全体をマネージ C# コードに変換することであるためです。

コードをコンパイルできる場合は、このテスト コードを使用して問題を再現できます。


問題

というタイトルの最新の変更セットは、moved resource management code to finalizers, to show bug私が抱えていた元の問題を示しています。このコードのすべてのクラスは、同じパターンを使用してアンマネージ リソースを解放します。以下に例を示します (C++/CLI):

DBContext::~DBContext()
{
    this->!DBContext();
    GC::SuppressFinalize(this);
}

DBContext::!DBContext()
{
    if(_pst.get() != nullptr)
        _pst.reset();            // _pst is a clr_scoped_ptr (managed type)
                                 // that wraps a shared_ptr<T>.
}

このコードには 2 つの利点があります。まず、このようなクラスがusingステートメントにある場合、リソースはすぐに適切に解放されます。第 2 に、ユーザーが破棄を忘れた場合、GC が最終的にクラスのファイナライズを決定したときに、管理されていないリソースが解放されます。

ここに、このアプローチの問題点があります。私が理解できないのは、時折、ファイル内のデータを列挙するために使用されるクラスの一部を GC がファイナライズすることを決定することです。これは多くの異なる PST ファイルで発生し、クラスがまだ使用されているにもかかわらず、Finalize メソッドが呼び出されたことに関係があると判断できました。

このファイル (ダウンロード) 1で一貫して発生させることができます。早期に呼び出されるファイナライザーは、 DBAccessor.cppファイル内のNodeIdCollectionクラスにあります。上記にリンクされたコードを実行できる場合 (このプロジェクトは、boost ライブラリへの依存関係のためにセットアップが難しい場合があります)、アプリケーションは例外で失敗します。これは、リストが null に設定され、ポインターがファイナライザーの実行の結果としてリセットされます。_nodes_db_

1) クラスの列挙コードに、NodeIdCollection使用中に GC がこのクラスをファイナライズするような明らかな問題はありますか?

以下で説明する回避策を使用して、コードを適切に実行することしかできませんでした。


見苦しい回避策

!classnameここで、すべてのリソース管理コードを各ファイナライザー ( ) からデストラクタ ( )に移動することで、この問題を回避することができました~classname。これで問題は解決しましたが、なぜクラスが早期にファイナライズされるのかという私の好奇心は解決されませんでした。

ただし、アプローチには問題があり、それよりも設計の問題であることは認めます。コード内でポインターが頻繁に使用されるため、ほぼすべてのクラスが独自のリソースを処理し、各クラスを破棄する必要があります。これにより、列挙型の使用が非常に見苦しくなります (C#):

   foreach (var msg in pst.Messages)
   {
      // If this using statement were removed, we would have
      // memory leaks
      using (msg)  
      {
             // code here
      }
   }

コレクション内のアイテムに作用する using ステートメントは、私には間違っているように思えますが、このアプローチでは、メモリ リークを防ぐことが非常に必要です。これがないと、pst クラスの dispose メソッドが呼び出されても、dispose が呼び出されず、メモリが解放されません。

私はこのデザインを変更しようとするあらゆる意図を持っています。このコードが最初に書かれたときの根本的な問題は、私が C++/CLI についてほとんどまたはまったく知らなかったという事実に加えて、ネイティブ クラスをマネージ クラス内に配置できなかったことです。クラスが使用されなくなったときにメモリを自動的に解放するスコープ付きポインターを使用できると思いますが、それが有効な方法であるかどうか、または機能するかどうかはわかりません。だから、私の2番目の質問は次のとおりです。

2) 管理されたクラスの管理されていないリソースを簡単に処理するための最良の方法は何ですか?

詳しく説明すると、ネイティブ ポインターをclr_scoped_ptr最近コードに追加されたラッパー (このstackexchangeの質問の clr_scoped_ptr.h )に置き換えることができますか? または、ネイティブ ポインターを or のようなものでラップする必要がありますか?scoped_ptr<T>smart_ptr<T>


これをすべて読んでくれてありがとう、私はそれがたくさんあったことを知っています。私よりも少し経験豊富な人からいくつかの洞察を得ることができるように、私は十分に明確であったことを願っています. それは非常に大きな質問です。私も可能であれば報奨金を追加するつもりです。うまくいけば、誰かが助けることができます。

ありがとう!


1このファイルは、自由に利用できるPST ファイルのエンロン データセットの一部です。

4

2 に答える 2

2

clr_scoped_ptrは私のもので、ここから来ています。

エラーがある場合は、お知らせください。

私のコードが完璧でなくても、マネージ コードであっても、スマート ポインターを使用することがこの問題に対処する正しい方法です。

ファイナライザーでa をリセットする必要はありません (すべきではありません) clr_scoped_ptr。それぞれclr_scoped_ptrがランタイムによってファイナライズされます。

スマート ポインタを使用する場合、独自のデストラクタまたはファイナライザを記述する必要はありません。コンパイラによって生成されたデストラクタは、すべてのサブオブジェクトのデストラクタを自動的に呼び出し、すべてのサブオブジェクト ファイナライザは収集時に実行されます。


コードをよく見ると、確かに にエラーがありますNodeIdCollectionGetEnumerator()各列挙がシーケンスの先頭から開始されるように、呼び出されるたびに異なる列挙子オブジェクトを返す必要があります。単一の列挙子を再利用しています。つまり、連続する への呼び出し間で位置が共有されGetEnumerator()ます。良くないね。

于 2011-10-15T22:57:22.063 に答える
-1

マイクロソフトのドキュメントから、デストラクタ/ファイナライザの記憶をリフレッシュすると、少なくともコードを少し簡素化できると思います。

あなたのシーケンスの私のバージョンは次のとおりです。

DBContext::~DBContext()
{
    this->!DBContext();
}

DBContext::!DBContext()
{
    delete _pst;
    _pst = NULL;
}

「GC::SupressFinalize」は C++/CLI によって自動的に行われるため、その必要はありません。_pst 変数はコンストラクターで初期化されるため (また、null 変数を削除しても問題は発生しません)、スマート ポインターを使用してコードを複雑にする理由がわかりません。

デバッグについてですが、"GC::Collect" への呼び出しをいくつか追加して、問題をより明確にするのを手伝っていただけないでしょうか。これにより、ぶら下がっているオブジェクトのファイナライズが強制されます。

これが少し役立つことを願って、

于 2011-10-15T19:43:22.980 に答える