7

C++ で memleak のないコードを書くことは、私にとっては問題ではありません。RAII のイディオムを守っているだけです

C# で memleak のないコードを記述することもそれほど難しくありません。ガベージ コレクターがそれを処理します。

残念ながら、C++/CLI コードを書くこと私にとって問題です。仕組みは理解できたと思いますが、まだ大きな問題があります。ヒントを教えていただければ幸いです。

これは私が持っているものです:

内部で C++ ライブラリ (OpenCV など) を使用する、C# で記述された Windows サービス。C++ クラスには、C++/CLI ラッパー クラスを使用してアクセスします。たとえばMatW 、画像オブジェクト用の C++/CLI ラッパー クラスがありcv::Mat、コンストラクター引数として a を受け取りますSystem::Drawing::Bitmap

public ref class MatW
{
public:
    MatW(System::Drawing::Bitmap ^bmpimg)
    {
        cv::Size imgsize(bmpimg->Width, bmpimg->Height);
        nativeMat = new Mat(imgsize, CV_8UC3);

        // code to copy data from Bitmap to Mat
        // ...
    }

    ~MatW()
    {
        delete nativeMat;
    }

    cv::Mat* ptr() { return nativeMat; }

private:
    cv::Mat *nativeMat;
};

別の C++ クラスは、たとえば

class PeopleDetector
{
public:
    void detect(const cv::Mat &img, std::vector<std::string> &people);
}

そしてそのラッパークラス:

public ref class PeopleDetectorW
{
public:
    PeopleDetectorW() { nativePeopleDetector = new PeopleDetector(); }
    ~PeopleDetectorW() { delete nativePeopleDetector; }

    System::Collections::Generic::List<System::String^>^ detect(MatW^ img)
    {
        std::vector<std::string> people;
        nativePeopleDetector->detect(*img->ptr(), people);

        System::Collections::Generic::List<System::String^>^ peopleList = gcnew System::Collections::Generic::List<System::String^>();

        for (std::vector<std::string>::iterator it = people.begin(); it != people.end(); ++it)
        {
            System::String^ p = gcnew System::String(it->c_str());
            peopleList->Add(p);
        }

        return peopleList;
    }

そして、これが私の Windows Service C# クラスのメソッドへの呼び出しです。

Bitmap bmpimg = ...
using (MatW img = new MatW(bmpimg))
{
    using (PeopleDetectorW peopleDetector = new PeopleDetector())
    {
        List<string> people = peopleDetector.detect(img);
    }
}

さて、ここに私の質問があります:

  • 私のコードに何か問題がありますか?
  • usingC# コードで使用する必要がありますか? 複数のラッパーオブジェクトが使用されている場合、usingステートメントをネストする必要があるため、コードが醜くなります
  • Dispose()オブジェクトを使用した後、代わりに使用できますか?
  • 気にせず、ガベージコレクターに任せることはできますか?(いいえusing、いいえDispose())
  • 上記のコードは、List<string^>^C++/CLI から C# などのオブジェクトを返す正しい方法ですか?
  • usinggcnewは、ガベージ コレクターがオブジェクトを処理することを意味するのではなく、オブジェクトをいつどのように解放するかを気にする必要はありませんか?

たくさんの質問があることは承知していますが、私が望むのはメモリリークを取り除くことだけなので、うまくいかない可能性があると思われるものをすべてリストしました...

4

2 に答える 2

6

私のコードに何か問題がありますか?

あなたが投稿したものではありません-usingステートメントを正しく適用しています。したがって、コード サンプルはメモリ リークの原因ではありません。

C# コードで using を使用する必要がありますか? using ステートメントをネストする必要があるため、複数のラッパー オブジェクトが使用されている場合、コードが見苦しくなります。

必要はありませんが、構文的にネストする必要はありません。これは同等です:

Bitmap bmpimg = ...
using (MatW img = new MatW(bmpimg))
using (PeopleDetectorW peopleDetector = new PeopleDetector())
{
    List<string> people = peopleDetector.detect(img);
}

オブジェクトを使用した後、代わりに Dispose() を使用できますか?

try可能ですが、例外がスローされた場合でも、 /が常に呼び出されるfinallyようにする必要があります。ステートメントは、そのパターン全体をカプセル化しますDisposeusing

気にせず、ガベージコレクターに任せることはできますか?(使用せず、Dispose() なし)

C++ RAII は一般に、コンストラクターでインクリメントされたカウンターのデクリメントなどを含む、あらゆる種類の一時的な状態のクリーンアップに適用されます。一方、GC はバックグラウンド スレッドで実行されます。RAII が処理できる決定論的なクリーンアップ シナリオのすべてに適しているわけではありません。RAII に相当する CLR はIDisposableで、それに対する C# 言語インターフェイスは ですusing。C++ では、それに対する言語インターフェイスは (当然のことながら) デストラクタ (Disposeメソッドになる) とdelete演算子 (への呼び出しになるDispose) です。C#「スタック上」で宣言された Ref クラス オブジェクトは、実際にはusing パターンとまったく同じです。

上記のコードは List^ のようなオブジェクトを C++/CLI から C# に返す正しい方法ですか?

かなり!

gcnew を使用しても、ガベージ コレクターがオブジェクトを処理するわけではなく、オブジェクトをいつどのように解放するかを気にする必要はありませんか?

メモリを解放する必要はありません。しかし、クラスが を実装している場合IDisposable、それはメモリ割り当てとはまったく別の問題です。Disposeオブジェクトを破棄する前に、手動で呼び出す必要があります。

ファイナライザーには注意してください。これらは、GC にフックして、GC がオブジェクトを収集するときに独自のクリーンアップ コードを実行する方法です。しかし、アプリケーション コードでの一般的な使用にはあまり適していません。それらは、制御できないスレッドから、制御できない時間に、制御できない順序で実行されます。そのため、あるオブジェクトのファイナライザーがファイナライザーを使用して別のオブジェクトにアクセスしようとすると、2 番目のオブジェクトは既にファイナライズされている可能性があります。これらのイベントの順序を制御する方法はありません。ファイナライザの本来の用途のほとんどは、現在ではSafeHandleでカバーされています。

于 2012-10-03T08:37:43.270 に答える
4

refクラスにファイナライザーがありません。

C ++ / CLIでは、スタック上にクラスのインスタンスを作成して(C ++スタイル)、スコープ外になるか、delete演算子を使用するときに、デストラクタが呼び出されます。ファイナライザーは、オブジェクトをファイナライズするときにGCによって呼び出されます。

C#では、GCがすべてのオブジェクトの破棄を処理するため(削除演算子はありません)、デストラクタとファイナライザの区別はありません。

したがって、〜を含む「デストラクタ」は、C#デストラクタのように動作するのではなく、C++デストラクタのように動作します。C ++ /CLIrefクラスの「デストラクタ」は.Netメソッドにコンパイルされます。 C#デストラクタ/ファイナライザーに相当するのは、ファイナライザーメソッドです。魔女は!で定義されます。(エクスクラメーション・マーク)。Dispose()

したがって、メモリリークを回避するには、ファイナライザーを定義する必要があります。

 !MatW()
    {
        delete nativeMat;
    }

 ~MatW()
    {
        this->!MatW();
    }

ビジュアルC++でのMSDNの記事デストラクタとファイナライザを参照して ください。

于 2012-10-03T07:42:26.543 に答える