1

ienumerable とその内容を削除するためのきちんとした小さな関数を作成しました (最近発見したメモリ リークをプラグインするため):

generic<typename T>
void CollectionHelpers::DeleteEnumerable(IEnumerable<T>^% enumerable)
{
      if(enumerable != nullptr)
      {
            for each( T obj in enumerable)
            {
                  delete obj;
            }

            delete enumerable;
            enumerable = nullptr;
      }
}

...しかし、何らかの理由で、デバッガーでトレースすると、関数から戻ったときに、リストがまだ一部のメモリを指しているように見えDeleteEnumerableます。

追跡参照として渡すことで、渡すハンドルを変更する必要があると思いましたか? ここで何を見逃したのですか?


編集:より完全なテスト例...

これは少し更新されたディスポーザーです。

using namespace GenericCollections;
using namespace System::Collections::Generic;
using namespace System;

generic<typename T>
void CollectionHelpers::DeleteEnumerable(IEnumerable<T>^% enumerable)
{
    if(enumerable != nullptr)
    {
        for each( T obj in enumerable )
        {
            Console::WriteLine("Disposing of object");
            delete obj;
        }

        Console::WriteLine("Disposing of enumerable");
        delete enumerable;
        enumerable = nullptr;
    }

    if( enumerable == nullptr )
    {
        Console::WriteLine("enumerable tracking reference is nullptr");
    }
    else
    {
        Console::WriteLine("enumerable tracking reference is NOT nullptr");
    }
}

これは、ディスポーザーを呼び出すコードの簡略化された例です。

using namespace System;
using namespace GenericCollections;
using namespace System::Collections::Generic;

ref class MyClass
{
private:
    int* m_myBuf;

public:
    MyClass()
    {
        m_myBuf = new int[100];
        Console::WriteLine("MyClass::MyClass()");
    }

    ~MyClass()
    {
        delete [] m_myBuf;
        m_myBuf = NULL;
        Console::WriteLine("MyClass::~MyClass()");
    }
};

int main(array<System::String ^> ^args)
{

    List<MyClass^>^ myList = gcnew List<MyClass^>;

    myList->Add(gcnew MyClass());
    myList->Add(gcnew MyClass());
    myList->Add(gcnew MyClass());

    CollectionHelpers::DeleteEnumerable(myList);

    if(myList == nullptr)
    {
        Console::WriteLine("Original list is disposed of");
    }
    else
    {
        Console::WriteLine(String::Format("Original list still referenced: {0}", myList));
    }

    return 0;
}

...そしてこれが出力です:

MyClass::MyClass()
MyClass::MyClass()
MyClass::MyClass()
Disposing of object
MyClass::~MyClass()
Disposing of object
MyClass::~MyClass()
Disposing of object
MyClass::~MyClass()
Disposing of enumerable
enumerable tracking reference is nullptr
Original list still referenced: System.Collections.Generic.List`1[MyClass]

正直なところ、列挙可能なものを削除しても、中に含まれているオブジェクトを削除する必要があるかどうかはあまり心配していません。私たちはこれらのリストをいたるところで使用しており、リストが十分に迅速にクリーンアップされていなかったため、システムのメモリが不足していました。

さらに悪いことに、リスト追跡参照を nullptr に設定する関数に依存していましたDeleteEnumerableが、それらが返されたときに追跡参照が更新されたようには見えません。コンソール出力からわかるように、デバッガーの問題だけではありません。

私が理解していないのは、なぜ

4

1 に答える 1

2

さて、この動作は確かに奇妙です。そして、これが実際に起こることです:

type のローカル変数があり、 typeList<MyClass^>^のパラメーターでメソッドを呼び出していますIEnumerable<T>^%。これは問題です。メソッドは、そのパラメーターを、たとえば に設定できますarray<T>^。C# ではこのようなコードは許可されませんが、何らかの理由で C++/CLI では許可されます。そして、次のようにコンパイルされます。

List<MyClass^>^ myList = …;
IEnumerable<MyClass^>^ enumerable = myList;
CollectionHelpers::DeleteEnumerable(enumerable);

問題が見えますか?nullptrinへの参照を設定するDeleteEnumerable()と、ローカル変数enumerableは変更されますが、 は変更されませんmyList

確認するには、コードを次のように変更します。

IEnumerable<MyClass^>^ myEnumerable = myList;

CollectionHelpers::DeleteEnumerable(myEnumerable);

if(myEnumerable == nullptr)
{
    Console::WriteLine("Original list is disposed of");
}
else
{
    Console::WriteLine(String::Format("Original list still referenced: {0}", myList));
}

そして、「元のリストは破棄されました」と正しく出力されます。

C++/CLI がこのように動作する理由を知りたい場合は、追跡参照が通常の C++ 参照と同様に動作するためでしょうか?

于 2012-02-19T13:30:36.847 に答える