Enhance your dynamic memory allocation in C++ with an undocumented
MFC class (CFixedAlloc)
Contents
- Include "fixalloc.h" in the header file that contains the class definition that you want to modify.
- Add the
DECLARE_FIXED_ALLOC() macro in the class declaration.
- Add the
IMPLEMENT_FIXED_ALLOC() macro in the CPP file that contains the class definition.
- Since
CFixedAlloc is a private MFC class, you have to add an additional include directory in the compiler options to point to the MFC source code directory. (I am assuming that you installed it during Visual Studio setup. It is installed by default, I think.)
- Recompile.
- Fine tune the size of the block size.
Now, you are done. Your class is upgraded for using the MFC fixed allocation service. One word of caution, if the define _DEBUG is present during the compilation, the CFixedAlloc macros will expand to nothing and the result will be that your class will behave as if you did no change to it. During the development of the demo program, I got this small problem where because I choose to link with the debug version of the run-time library, the _DEBUG macro was implicitly set. Be aware of that. To figure that out, I added garbage in this block that MFC automatically adds when it creates a new file:
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
Also, here is a small comment on the last step. It is very important that you provide a good value for the block size. If it is too big, you will be wasting memory, and if it is too small then you will not get as much performance improvements as you could get. However, even if the block size value is too small, you will still reduce the number of CRT allocation calls by a factor equal to the block size which is non negligible. The ideal value for the block size is the exact number of objects that will be allocated like in the demo program, but of course it is usually not possible to know that value.
It has been reported that when compiling the demo program with VC++2005, the
user is getting the following warnings:
.\CFixedAllocDemoDlg.cpp(237) : warning C4995: 'CFixedAlloc': name was marked as #pragma deprecated
.\CFixedAllocDemoDlg.cpp(240) : warning C4995: 'CFixedAlloc': name was marked as #pragma deprecated
Since that I am not aware of any MFC class that would have superseded
CFixedAlloc and after reading this article, you should know what does the class
CFixedAlloc and how, my recommendation, if using CFixedAlloc in your program
makes a difference, is that you can safely ignore the warnings. If you feel
extra cautious and you really want to make sure that your program will compile
with future versions of MFC in case Microsoft removes the CFixedAlloc class from
MFC, nothing stops you from making a private copy of the CFixedAlloc files.
This is true that Microsoft is slowly moving away from CFixedAlloc . In MFC6,
CFixedAlloc is used with every temporary handle maps objects classes (ie:
CWnd and CGdiObject ). Here is an example:
class CTempGdiObject : public CGdiObject
{
DECLARE_DYNCREATE(CTempGdiObject)
DECLARE_FIXED_ALLOC(CTempGdiObject);
};
Such classes have been removed in MFC7 (VC++2003). I am really curious about
what motivates the MFC team of doing so and if someone knows, I would really
appreciate if you could share that information.
CFixedAlloc returns a node in a "free list" when an object is deleted but the "free list" is inside the same allocated CPlex . Since plexes are single linked, they will remain allocated for the entire duration of the program (until their head is deleted). In fact, CFixedAlloc is more than an allocator, it is a recycler: it allocates on need but does not release. It just keeps apart for eventual subsequent allocation requests.
This results in better performance speed in case of frequent alternate new / delete / new calls (since you will mostly reuse the system resource you already own), but may create problems in those situations when a huge amount of memory is required only for a limited time: the program will retain the memory until the head of the plexes is deleted (and since it is static ... at the end of the program) unless the user explicitly releases the memory with CFixedAlloc::FreeAll() once he is done with the memory. You can call CFixedAlloc::FreeAll() anytime to free yourself the memory if you have previously deleted all the allocated objects. Otherwise, the memory will be freed when the CFixedAlloc object is destroyed. Here is a little reminder: Global objects are created in startup code before entering WinMain() . Global objects are destroyed after the program has exited WinMain() and right before the program gets unloaded.
When using CFixedAlloc with the derived class, there are a few things that you must be aware of. Let's say you have:
class Base
{
DECLARE_FIXED_ALLOC(Base);
};
class Child : public Base
{
private:
// Some data members declared.
};
If you do:
Child *p = new Child;
Base::operator new() will be called and because the class
Child does not have the same size than the class Base ,
CFixedAlloc will not work.
This problem is discussed with great details in the book Effective C++ and to
prevent that problem, this
is also why there is an ASSERT() statement in the
overloaded operator new in the DECLARE_FIXED_ALLOC() macro.
However, do not rely on the ASSERT() macros to catch potential
errors in your code because they are useless! Since the ASSERT()
macro is active only in debug mode they are of no use because in debug mode the
macro DECLARE_FIXED_ALLOC() also expand to nothing. That being said, nothing stops you from using CFixedAlloc in a Base class *and* its derived classes.
class Base
{
DECLARE_FIXED_ALLOC(Base);
};
class Child : public Base
{
DECLARE_FIXED_ALLOC(Child);
};
Of course, it doesn't make sense to declare the Base class as FIXED_ALLOC if the class is abstract . One thing to be aware about using CFixedAlloc in class hierarchy is that the operator delete is static and not virtual . Consider the following example:
Base *p = new Child;
delete p;
In this case, Base::operator delete() would be called (I haven't tested it but I'm 99% confident that this is what would happen) and that would be a serious problem. To workaround that problem here is a potential solution:
class Base
{
public:
virtual void Destroy() {delete this;}
DECLARE_FIXED_ALLOC(Base);
};
class Child : public Base
{
public:
virtual void Destroy() {delete this;}
DECLARE_FIXED_ALLOC(Child);
};
Base *p = new Child;
p->Destroy();
Page
1
2
3
4
|