Click here to Skip to main content
Click here to Skip to main content

A better memory managed MFC CArray

By , 17 Mar 2002
 

Introduction

When I used MFC's CArray in one of my projects, I found it's not very heap-friendly.

It does use a pre-allocated memory pool but it doesn't use it clearly. For example, it doesn't have a constructor that allow us to explicitly declare the size of our pre-allocated memory pool. Furthermore, I think MFC CArray doesn't make clear between the actual memory pool size and the elements size of the array.

And if we really need to "pre-allocate", we has to make use of its grow-by variable, which is unclear and confusing.

How to use

Include the two files NewBValArray.h and NewBConfig.h in your project

Declare a variable:

#include "NewBValArray.h"
NewBValArray<MyClass,MyClass> rgMyArray(1000);

So rgMyArray will pre-allocate a memory pool of 1000 MyClass items.

NOTE: rgMyArray is still a blank array, i.e rgMyArray.GetSize() = 0; as long as you don't "add" items to it through Add(), or oprerator[].

Otherwise, it can be used the same as MFC's CArray, i.e you can safely replace all CArray with NewBArray.

Why don't I use MFC CArray?

After my posting, many ask me why I didn't use MFC's CArray?

I do know that MFC CArray can pre-allocate memory. But it really doesn't work as I expect.

What do I expect? I'd like to separate the holder (memory) and the meaning of array elements. That means I'll allocate a big chunk of memory to hold the elements' data. This memory should be allocated in the array's ctor and delete at array's dtor. During its lifetime, this part of memory shouldn't be changed

Now let's dive into the MFC CArray source code. It's contains 4 variables:

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

Now, let see how can we force CArray to pre-allocate memory by SetSize(int nNewSize, int nGrowBy). nNewSise is the new desired size. nGrowBy I'll explain later. Then assume that we have

CArray<MyClass,MyClass> rgMFCArray;

What if I do this:

rgMFCArray.SetSize(1000,0);

Now I expect that rgMFCArray has 1000 pre-allocated spaces for MyClass, means I can Add to it 1000 MyClass variables. But actually rgMFCArray now contains 1000 MyClass "default" variables and if I call rgMFCArray.Add(...) then the m_pData will need to reallocate to a 1001 MyClass space : not what I expect.

Again, what if I do this:

rgMFCArray.SetSize(0,1000);

Now I make use of the grow-by stuff. At this time rgMFCArray will assign its m_nGrowBy = 1000. That's all! No allocation of memory. So if you do rgMFCArray[1], you'll get ASSERT false.

Then what if:

rgMFCArray.Add(someMyClassvariable);

Yeah, this time it's worked. Now CArray will allocate a memory chunk of size 1000*sizeof(MyClass). So if I really like to make use of pre-allocate memory on MFC's CArray, I have to use its m_nGrowBy variable. So confusing!

More! MFC's Array doesn't provide you any method to "remove" all CArray elements or change its memory allocation.

You can ask me about CArray::RemoveAll(). This method will remove all array elements plus deallocate its memory.

But really,we can remove all elements of MFC CArray with a long statement

for (int i = rgMFCArray.GetCount() - 1; i >= 0 ; i --)
   rgMFCArray.RemoveAt(i)

Again, it make me confused and uneasy.

So that's why I rewrite my own array class by just "hacking" MFC's CArray source, cause really MFC CArray is still a good boy, especially its making use of placement new and delete.

In my new class, I explicitly separate m_nSize (number of elements) and m_nMaxSize (the size of pre-allocated memory pool). I added a ctor that has one parameter to declare the size of pre-allocated memory, and this memory I can take from my own allocator as in many projects. And the method RemoveAll() just effectively "removes" the elements, i.e calls their dtor but leaves the memory pool untouched. This memory pool will be cleaned at NewBArray's dtor. Moreover, I still keep the ability to "grow" the memory pool to a larger one if the user adds more elements the array capacity. But this action shouldn't happen because it makes the heap fragment and cause memory allocation, and copy overhead.

This code is written by Nguyen Binh. I greatly appreciate any feedback.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Nguyen Binh
Web Developer
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralBug fixmembermerckel22 May '05 - 19:47 
I think there is a bug. If we do not pre-allocate enough memory, then, when we call Add, in SetAtGrow, we will call SetSize.
In that case m_nSize is updated inside SetSize. However we update again (by incrementing) this value inside Add and I think we should not (GetSize will return a wrong value...).
 
We can temporarily fix that bug with:
 
template<class TYPE, class ARG_TYPE>
NEWB_INLINE NEWB_INT NewBValArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement)
{ 
   int nIndex = m_nSize;
 
   BOOLEAN flSetSize = FALSE ;
   if (nIndex >= m_nMaxSize)
      flSetSize = TRUE ;
 
   SetAtGrow(nIndex, newElement);
 
   // If we call SetSize to add the new element, m_nSize will be updated
   if (!flSetSize) 		
      m_nSize ++;
 
   return nIndex; 
}
 
However, it looks like "a piece of scotch tape"! (it's why I wrote temporarilySmile | :) )
GeneralWhy not use CMyTypedPtrListmemberSimon Hughes24 Mar '02 - 22:47 
Have a look at http://www.codeproject.com/cpp/smartlist.asp
 
Regards,
Simon Hughes
E-mail: simon@hicrest.net
Web: www.hicrest.net
GeneralPurpose of CArraymemberZac Howland20 Mar '02 - 4:48 
CArray (and all of its derivitives) were not designed for what you were trying to use it for. CArray is designed to hold and allocate memory for only what is currently in the array. For that reason, what you were trying to do does not work correctly.
 
Also, the Growby variable works as follows. If I have to allocate more memory (ie I add an item to the array and it makes my Size > Capacity), the CArray object will allocate space for m_nGrowby objects. That is why when you set the size with the growby and then add an item, you see the behavior you want.
 
Both of these approaches have their advantages and disadvatanges as compared with both your class and the vector template. One of the major advantages is the fact that with CArray you typically do not allocate much more space than you use. For example, lets say I was storing 10,000 objects in my array and my Growby is set to 1000 (thus, every time I excede n * 1000, where n is some constant, I allocate memory for 1000 more objects), then at most I will allocate 999 memory blocks for my objects (using the term blocks loosely here). Now, for the example you gave, if we stopped at 1, this isn't very efficient, however, with large arrays it works well.
 
Zac Howland
GeneralRe: Purpose of CArraymemberNguyen Binh22 Mar '02 - 17:55 
."CArray is designed to hold and allocate memory for only what is currently in the array"
 
-Hmm.. not really, I see MFC CArray do use extra space.
 
.You are right with GrowBy, but I think it's not very clear in MFC CArray now.
 
.Again, with CArray, you can still have a 10000 memory item size blocks hold 1 item.
 
So what I do here is just explicitly point out what we need. If I know my array only contains a few items then for sure I don't preallocate it to 10000 item size. And even if I do waste so much space, I can change my mind by calling FreeExtra() function to free all the blank spaces, and surely, it'll have to pay a little overhead of mem de/alloc.

QuestionWhy not using CArray::SetSize?memberIngo Kellpinski18 Mar '02 - 3:10 
I think the functionality you're looking for is already present: CArray::SetSize will pre-allocate the memory. Thus, you don't need the GrowBy stuff.
 
By the way: Why do you introduce all these config typedefs? All types are already defined multiple times under WIN32. Now everybody has to deal with "NewBConfig.h" and this is not desirable!
 
Example:
- min, max, BOOL, BYTE, WORD, ...: WinDef.h
- ASSERT: AFX.h (for MFC)
- assert: assert.h
 
Best regards,
Ingo.

AnswerRe: Why not using CArray::SetSize?memberNguyen Binh18 Mar '02 - 16:34 
For your first question, please refer back to the article. I've updated it.
 
For your second one: I plan to use NewBArray in many project : MFC, non-MFC ,and other platforms (hopefully Poke tongue | ;-P ). So I try to make it as independent as I could. So NewBConfig.h is just do that task. If you only wish to use in MFC, then you can Crlt-H "search an replace" all my typedef with Win32 default types, like NEWB_INT with INT,..etc..
 
BR.
 
Nguyen Binh.

GeneralRe: Why not using CArray::SetSize?memberBernhard18 Mar '02 - 22:28 
and why not stl if you want platform indepentness?
okay.. it may be memory consuming too.. but it works like a flirt..
bernhard
 

Sometimes I think the surest sign for intelligent life elsewhere in
the universe is that none of them ever tried to contact us.
GeneralRe: Why not using CArray::SetSize?memberJörgen Sigvardsson18 Mar '02 - 23:10 
Bernhard wrote:
but it works like a flirt..
 
Does that mean that it can fade into nothing, it may get laid, or perhaps even get children, house, cars, and a bunch of toddlers? Big Grin | :-D
 
Sonorked as well: 100.13197 jorgen
FreeBSD is sexy.
GeneralRe: Why not using CArray::SetSize?memberBernhard18 Mar '02 - 23:19 
sure.. and the homevideo is sold under "vector - hot and horny"...
 
i don't know what i should think bout people who think that a program could be sexy (loathing?)
bernhard
 

Sometimes I think the surest sign for intelligent life elsewhere in
the universe is that none of them ever tried to contact us.
GeneralRe: Why not using CArray::SetSize?memberJörgen Sigvardsson18 Mar '02 - 23:25 
Bernhard wrote:
i don't know what i should think bout people who think that a program could be sexy (loathing?)
 
First off; no animals or humans are hurt in the process Wink | ;)
Second; take a look at the FreeBSD source tree, how well it works (in an UNIX context) and how nice it is being able to hack up some fixes (kernel/userland) yourself.
 
So you don't need to loathe me. Perhaps have some pity for me Wink | ;)
 
Sonorked as well: 100.13197 jorgen
FreeBSD is sexy.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 18 Mar 2002
Article Copyright 2002 by Nguyen Binh
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid