In
C++, you can create arrays of objects on the stack or on the heap with equal
ease, and (of course) the constructor is called for each object in the array.
There’s one constraint, however: There must be a default constructor,
except for aggregate initialization on the stack (see Chapter 3), because a
constructor with no arguments must be called for every object.
When
creating arrays of objects on the heap using
new,
there’s something else you must do. An example of such an array is
MyType*
fp = new MyType[100];
This
allocates enough storage on the heap for 100
MyType
objects and calls the constructor for each one. Now, however, you simply have a
MyType*,
which is exactly the same as you’d get if you said
MyType*
fp2 = new MyType;
to
create a single object. Because you wrote the code, you know that
fp
is actually the starting address of an array, so it makes sense to select array
elements with
fp[2].
But what happens when you destroy the array? The statements
delete fp2; // OK
delete fp; // Not the desired effect
look
exactly the same, and their effect will be the same: The destructor will be
called for the
MyType
object pointed to by the given address, and then the storage will be released.
For
fp2
this is fine, but for
fp
this means the other 99 destructor calls won’t be made. The proper amount
of storage will still be released, however, because it is allocated in one big
chunk, and the size of the whole chunk is stashed somewhere by the allocation
routine.
The
solution requires you to give the compiler the information that this is
actually the starting address of an array. This is accomplished with the
following syntax:
delete
[]fp;
The
empty brackets tell the compiler to generate code that fetches the number of
objects in the array, stored somewhere when the array is created, and calls the
destructor for that many array objects. This is actually an improved syntax
from the earlier form, which you may still occasionally see in old code:
delete
[100]fp;
which
forced the programmer to include the number of objects in the array and
introduced the possibility that the programmer would get it wrong. The
additional overhead of letting the compiler handle it was very low, and it was
considered better to specify the number of objects in one place rather than two.
Making
a pointer more like an array
As
an aside, the
fp
defined above can be changed to point to anything, which doesn’t make
sense for the starting address of an array. It makes more sense to define it as
a constant, so any attempt to modify the pointer will be flagged as an error.
To get this effect, you might try
int
const* q = new int[10];
or
const
int* q = new int[10];
but
in both cases the
const
will bind to the
int,
that is, what is being pointed
to,
rather than the quality of the pointer itself. Instead, you must say
int*
const q = new int[10];
Now
the array elements in
q
can be modified, but any change to
q
itself (like
q++)
is illegal, as it is with an ordinary array identifier.