内存管理更好的 MFC CArray






2.44/5 (5投票s)
一个使用内存池的、对堆更友好的模板 CArray。
引言
当我在我的一个项目中使用 MFC 的 CArray 时,我发现它对堆不太友好。
它确实使用了预先分配的内存池,但并没有清晰地使用它。例如,它没有一个允许我们显式声明预分配内存池大小的构造函数。此外,我认为 MFC CArray 没有明确区分实际内存池大小和数组的元素大小。
而且,如果我们真的需要“预分配”,就必须使用它的 grow-by 变量,这既不清晰又令人困惑。
如何使用
在您的项目中包含这两个文件:NewBValArray.h 和 NewBConfig.h
声明一个变量
#include "NewBValArray.h" NewBValArray<MyClass,MyClass> rgMyArray(1000);
因此,rgMyArray
将预分配一个包含 1000 个 MyClass
项的内存池。
注意:只要您不通过 Add()
或 oprerator[]
向其“添加”项,rgMyArray 仍然是一个空数组,即 rgMyArray.GetSize() = 0;
。
否则,它可以像 MFC 的 CArray 一样使用,也就是说,您可以安全地用 NewBArray 替换所有 CArray。
我为什么不使用 MFC CArray?
在我的帖子发布后,很多人问我为什么不使用 MFC 的 CArray?
我知道 MFC CArray 可以预先分配内存。但它并没有真正按照我的期望工作。
我期望什么?我希望将持有者(内存)和数组元素的含义分开。这意味着我将分配一大块内存来保存元素的数据。这块内存应该在数组的 ctor 中分配,在数组的 dtor 中删除。在其生命周期内,这部分内存不应被更改
现在让我们深入研究 MFC CArray 的源代码。它包含 4 个变量
TYPE* m_pData; // the actual array of data int m_nSize; // # of elements (upperBound - 1) int m_nMaxSize; // max allocated int m_nGrowBy; // grow amount
现在,让我们看看如何通过 SetSize(int nNewSize, int nGrowBy)
强制 CArray 预分配内存。nNewSise
是新的所需大小。稍后我将解释 nGrowBy
。然后假设我们有
CArray<MyClass,MyClass> rgMFCArray;
如果我这样做
rgMFCArray.SetSize(1000,0);
现在我希望 rgMFCArray 预先分配了 1000 个 MyClass
的空间,这意味着我可以向其中添加 1000 个 MyClass
变量。但实际上,rgMFCArray
现在包含 1000 个 MyClass
的“默认”变量,如果我调用 rgMFCArray.Add(...)
,那么 m_pData
将需要重新分配到一个 1001 个 MyClass
的空间:这并非我所期望的。
再说一次,如果我这样做
rgMFCArray.SetSize(0,1000);
现在我使用了 grow-by 的东西。此时,rgMFCArray
将赋值它的 m_nGrowBy = 1000
。就这样!没有内存分配。因此,如果您执行 rgMFCArray[1]
,您将得到断言失败。
那么如果
rgMFCArray.Add(someMyClassvariable);
是的,这次它奏效了。现在 CArray
将分配一个大小为 1000*sizeof(MyClass)
的内存块。所以如果我真的想在 MFC 的 CArray
上使用预分配内存,我必须使用它的 m_nGrowBy
变量。太令人困惑了!
更糟糕的是!MFC 的 Array 没有提供任何方法来“删除”所有 CArray
元素或更改其内存分配。
您可能会问我关于 CArray::RemoveAll()
的问题。此方法将删除所有数组元素并释放其内存。
但实际上,我们可以使用一个长语句删除 MFC CArray 的所有元素
for (int i = rgMFCArray.GetCount() - 1; i >= 0 ; i --) rgMFCArray.RemoveAt(i)
再说一遍,这让我感到困惑和不安。
这就是为什么我通过“黑”MFC 的 CArray 源代码来重写我自己的数组类,因为实际上 MFC CArray 仍然是一个好孩子,特别是它使用了 placement new 和 delete。
在我的新类中,我显式地将 m_nSize
(元素数量)和 m_nMaxSize
(预分配内存池的大小)分开。我添加了一个 ctor,它有一个参数来声明预分配内存的大小,我可以从我自己的分配器中获取这部分内存,就像在许多项目中一样。而 RemoveAll()
方法只是有效地“删除”了元素,即调用它们的 dtor,但保持内存池不变。这个内存池将在 NewBArray 的 dtor 中清理。此外,我仍然保持了“增长”内存池到更大大小的能力,如果用户添加了更多元素到数组容量。但是,这种操作不应该发生,因为它会使堆碎片化并导致内存分配和复制开销。
这段代码由阮平编写。我非常感谢任何反馈。