Array Allocation



Item 37. Array Allocation

Most C++ programmers know to keep the array and nonarray forms straight when allocating and deallocating memory.

T *aT = new T; // non-array
T *aryT = new T[12]; // array
delete [] aryT; // array
delete aT; // non-array
aT = new T[1]; // array
delete aT; // error! should be array              

The reason it's important to keep these functions properly paired is that array allocation and deallocation use different functions from nonarray allocation and deallocation. A new expression does not use operator new to allocate storage for an array. It uses array new. Similarly, a delete expression does not invoke operator delete to free an array's storage; it invokes array delete. To be precise, when you allocate an array, you're using a different operator (new[]) than you do when you allocate a nonarray (new), and likewise for deallocation.

Array new and array delete are array analogs of operator new and operator delete and are declared similarly:

void *operator new( size_t ) throw( bad_alloc ); // operator new
void *operator new[]( size_t ) throw( bad_alloc ); // array new
void operator delete( void * ) throw(); // operator delete
void operator delete[]( void * ) throw(); // array delete

The most common source of confusion with the array forms of these functions occurs when a particular class or hierarchy defines its own memory management with member operator new and operator delete (see Class-Specific Memory Management [36, 123]).

class Handle {
  public:
    //...
    void *operator new( size_t );
    void operator delete( void * );
    //...
};

The Handle class has defined nonarray memory management functions, but these won't be called for an array of Handles; the global array new and array delete will:

Handle *handleSet = new Handle[MAX]; // calls ::operator new[]
//...
delete [] handleSet; // calls ::operator delete[]

Logically, it would seem to be a good idea always to declare the array forms of these functions whenever the nonarray forms exist (though, strangely, it doesn't appear to be a common practice). If the intent is really to invoke the global array allocation operations, it's clearer if the local forms just forward the call:

class Handle {
  public:
    //...
    void *operator new( size_t );
    void operator delete( void * );
    void *operator new[]( size_t n )
        { return ::operator new( n ); }
    void operator delete[]( void *p )
        { ::operator delete( p ); }
    //...
};

If the intent is to discourage the allocation of arrays of Handles, then the array forms can be declared to be private and left undefined (see Restricting Heap Allocation [34, 117]).

A second source of confusion and error concerns the value of the size argument that is passed to array new depending on how the function is called. When operator new is called (implicitly) in a new expression, the compiler determines how much memory is required and passes that amount as the first argument to operator new. That amount is the size of the object being allocated:

aT = new T; // calls operator new( sizeof(T) );

It's also possible to call operator new directly, in which case we must specify the number of bytes we want to allocate explicitly:

aT = static_cast<T *>(operator new( sizeof(T) ));

We can also call array new directly:

aryT = static_cast<T *>(operator new[]( 5*sizeof(T) ));

However, when we call array new implicitly through a new expression, the compiler may, and often does, increase the memory request by a small amount.

aryT = new T[5]; // request 5*sizeof(T) + delta bytes

The additional space is generally used by the runtime memory manager to record information about the array that is necessary to later reclaim the memory (the number of elements allocated, the size of each element, and so on). To further complicate the situation, the compiler may or may not request this additional space for every allocation, and the size of the request may vary from allocation to allocation.

This difference in the amount requested is typically only a concern in very low-level code, where storage for arrays is being handled directly. If you're going to be low level, it's generally simplest to avoid direct calls to array new and the associated meddling by the compiler and use plain old operator new instead (see Placement New [35, 119]).