April 90 - USING C++ OBJECTS IN A HANDLE-BASED WORLD
USING C++ OBJECTS IN A HANDLE-BASED WORLD
ANDY SHEBANOW
Although C++ is a powerful and flexible language, its image of the world inside your
computer was shaped by operating systems that were a bit more "traditional" than the
Macintosh's. As a result, C++ routines that work fine for MPW tools can cause severe
problems when used in a Macintosh application. But there are ways around these
problems. This article describes a technique that allows you to use normal C++
objects in your Macintosh applications without undue discomfort.
Using C++ objects in the handle-based world of the Macintosh Memory Manager can
get pretty tricky at times. The Apple extension to C++ that solves the memory
allocation problems you're bound to run into can create other headaches for you if you
need to use one or more of several important C++ features in your program. In this
article, you'll learn about the memory allocation problems you can expect to encounter
when you create objects in C++. You'll also learn how to get around these problems
while still retaining the use of important C++ features, by creating a special class
PtrObject. You'll see a sample program that uses PtrObject, and you'll learn how to
implement the class.
PROBLEMS WITH MEMORY ALLOCATION FOR OBJECTS IN C++
In C++, objects are created dynamically with the new operator, and disposed of with
the delete operator when you've finished with them, like this:
TMyObject* aMyObjectRef;
aMyObjectRef = new TMyObject; // Create a TMyObject object.
aMyObject->AReallyCoolRoutine(); // Do something useful...
delete aMyObject; // Delete the object.
When you use these operators, C++ transforms them into calls to operatornew and
operator delete. The default versions of operatornewand operatordeleteprovided
in the C++ library use the C Standard Library routines malloc and free,
respectively, to allocate and deallocate the memory needed to store the object.
(Actually, the calloc routine is called to allocate the memory, but callocjust turns
around and calls malloc to do the real work.)
These routines work fine for MPW tools, but they can cause the following severe
problems when used in a Macintosh application:
Heap fragmentation. Nonrelocatable memory for your objects can be
allocated in the middle of your heap, preventing the Mac's Memory Manager
from compacting memory properly.
Heap space permanently wasted. Because calloc and free manage their own
list of free memory blocks and never return unused space to the Mac's
Memory Manager, if you create a lot of C++ objects your program can run out
of memory and crash even though you have plenty of free memory available.
(See the sidebar "Everything You Didn't Want to Know About malloc and free
for more information on malloc internals.)
Fortunately, you can override the default versions of operatornewand
operatordelete in your own classes to get explicit control over memory allocation.
To help you do this, Apple extended C++ to include a predefined base class called
HandleObject. If classes you define inherit from HandleObject, the Mac's
NewHandle and DisposHandletraps are called instead of the default operator new
and operator delete routines.
While this solves the memory problems just mentioned, it also precludes the use of
several important C++ features. Here is a partial list of the restrictions that apply
when classes you define inherit fromHandleObject:
• It is an error to declare global variables, local variables, arrays,
members, or param- eters of handle-based classes (rather than pointers to
them).
• Multiple inheritance cannot be used with handle-based classes.
• Handle-based objects can be created only by the new operator. The only
use of a dereferenced handle-based class pointer (for example, *x) is to refer
to a field in the class (for example, *x.y or x->y).
• It is not possible to allocate an array of handle-based objects--for
example, new->MyObjects[10].
As you can see, there are quite a few useful things you can't do in C++ if you use
HandleObject. Most programs should be able to live with these restrictions, but if
your program needs to use multiple inheritance or arrays of objects, a different
solution is called for.
EVERYTHING YOU DIDN'T WANT TO KNOW ABOUT MALLOC AND
FREE
Why do the malloc and free routines wreak so much havoc in a Macintosh
application? The main reason is that these routines were originally written for UNIX
systems, which have no built-in memory allocation facilities. So these library
routines ended up doing everything themselves, including free list management.
This isn't all bad, since these routines are simpler and faster than their Macintosh
Memory Manager equiva- lents, but they can cause the severe problems listed earlier
in this article for a Macintosh application. The worst part is that these problems can
occur even if your application doesn't callmalloc directly. In many situ- ations, C++
callsmalloc for you, as do many of the other routines in the standard library.
Here's how it all works (in MPW, at least):
When you request some memory frommalloc, it rounds the size up to the nearest
power of 2 (8-byte mini- mum, ID checked at the door). If you ask for more than
2048 bytes, malloc just calls NewPtr to allocate the memory, and DisposPtr to get
rid of it. Otherwise,malloc checks its internal free list looking for blocks of the
specified size. If it doesn't find any blocks of that size, it allocates a chunk of memory
with NewPtr big enough to hold 2K worth of blocks (plus 2 bytes overhead per block),
and adds the new blocks to the free list for that size. It then returns you the first block
off of the free list.
When you dispose of memory with the free routine, it looks at the block header to
determine which free list to put the block in, and inserts it into the list (sorted by
block address to allow for more intelligent freestore management in the future).
Here's what a small free list looks like:
MPW (and other Mac development systems) provides versions of these routines to
make life easier for people who are porting code from UNIX systems (or MS-DOS,
OS/2, etc.). However, since the mallocroutine calls NewPtr rather indiscrimately, it
can cause blocks to be allocated in very inconvenient places inside your heap, and once
these blocks have been allocated, they are never disposed of.
In just one possible scenario, the user opens a large document with your program
SuperOOPWrite and cre- ates 1000 standard C++ objects (each allocated by malloc)
to represent the elements of the document, each about 100 bytes long. malloc asks
NewPtr to create 63 blocks of memory (about 2K each), and you have about 100K less
free memory than you used to. Now the user closes the document, and you dutifully
dispose of all of your objects. Guess what? You still have 100K less memory available
to you as far as the Mac's Memory Manager is concerned, your heap is chock full of 2K
nonrelocatable blocks, and you don't have any way to preflight memory for the next
time the user wants to open a document.
By the way, if this algorithm sounds familiar to you, it's because the MPW code is
based on a public domain version of malloc written by Chris Kingsley.
THE SOLUTION: CREATING A SPECIAL CLASS PTROBJECT
The solution to memory allocation problems when you can't use HandleObject is to
create a special class PtrObject analogous to the HandleObject class. This class
overrides both operatornewand operatordelete, so that real Memory Manager
pointers are used instead of the pointers returned by the default operatornew.
PtrObjectalso supports the allocation of objects into a separate heap, which further
reduces memory fragmentation.
The method functions of the class PtrObject are as follows:
AllocHeap This function creates a separate heap. All descendants of class
PtrObject cre- ated after calling this function will use this
heap. If you do not call this function in your program, the
default (application) heap will be used.
DisposeHeap This function disposes of the heap allocated by a previous call
to AllocHeap. You should call this function before quitting
your application. Any PtrObjects created inside the heap
will be invalid, so make sure that you aren't using any of
those objects anymore (neither operatordelete nor the
destructor for these objects will be called).
FreeMemory This function returns the amount of free space in the
PtrObject heap, as returned by the trap FreeMem. If no
separate heap exists, this function will return the amount of
free memory in the default (application) heap.
MaxMemory This function returns the size of the largest free block in
the PtrObject heap, as returned by the trap MaxMem. If no
separate heap exists, this function will return the amount of
free memory in the default (application) heap.
operator new This function is called by the C++ compiler to allocate
memory for PtrObjects. You never need to call it directly.
operator delete This function is called by the C++ compiler to deallocate
memory used by PtrObjects. You never need to call it
directly.
Here is the class declaration for PtrObject, which would normally be found in the
header file PtrObject.h.
class PtrObject {
public:
static OSErr AllocHeap(size_t heapSize);
// Create a heap heapSize bytes long to allocate
// objects in.

static void DisposeHeap();
// Free up the heap allocated by a previous call
// to AllocHeap.
static long FreeMemory();
// Return the total amount of free space in the heap.
static Size MaxMemory();
// Return the size of the largest free block in the heap.

void* operator new(size_t size);
void operator delete(void* p);
// These are our special allocation and
// deallocation operators.
private:
static THz fZone;
// Our private zone pointer.
};
Notice that the AllocHeap, DisposeHeap, FreeMemory, and MaxMemorycalls are all
static member functions, and that the fZone variable is a static data member . In
C++, static members are shared across all instances of a class. You should use static
members in place of global variables and functions whenever possible, since they have
limited scope (which means fewer name conflicts) and they are logically tied to the
class in which they are declared (which means more readable source code). To call a
static member function, the syntax is