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]

老实说,我并不担心删除可枚举的作品是否是其中包含的需要被杀死的对象。我们到处使用这些列表,但我们的系统内存不足,因为它们没有被足够快地清理干净。

更糟糕的是,我们依赖该DeleteEnumerable函数将列表跟踪引用设置为 nullptr,但是当它们被传回时,跟踪引用似乎没有更新。从控制台输出中可以看出,这不仅仅是调试器问题。

我不明白的是为什么

4

1 回答 1

2

好吧,这种行为确实很奇怪。这就是实际发生的情况:

您有一个类型的局部变量,List<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 回答