A dynamic array does not stretch when its capacity variable changes. To keep more elements, allocate a replacement array, copy the needed values, release the old array, and update the owning pointer. These steps also help explain pointer arithmetic and separately allocated rows.
Read CS1: allocation and lifetime and CS1: grid indexing if either storage ownership or multiple indexes is unfamiliar. Each allocation should have a clearly identified base address, length, and cleanup responsibility.
Trace replacement storage before changing ownership #
Suppose capacity is three and the active values are two, eight, nine. Used is three, and a request asks for capacity six. The new storage must hold at least all three active values, expressed by new_capacity >= used.
int* larger = new int[new_capacity]{};
for (int i = 0; i < used; ++i) larger[i] = values[i];
delete[] values;
values = larger;
capacity = new_capacity;The first line allocates the replacement and initializes its integers to zero. At this moment, both the old and new arrays exist. Larger refers to the new array; values still refers to the old one. Allocation itself has not transferred any old data.
The loop copies indexes zero, one, and two. Afterward, the new array starts with two, eight, nine, while its remaining slots stay zero-initialized. Used is still three. The new spare positions exist, but they are not automatically meaningful observations.
Next, the old array is deleted. Only after its values have been preserved does the owning variable take the replacement address. Finally, capacity records the new allocated size. The separate local pointer larger also contains that address; copying it into values did not allocate a second replacement array.
Why the order matters #
Deleting first would destroy the source values before the copy. Changing only capacity would claim extra storage without creating any. Setting the owning pointer to the replacement too early could also lose the address needed to release the old array.
Any pointer or reference into the old allocation becomes invalid when that allocation is released. A remembered address to its second element does not redirect itself to the second element of the new array. Recompute such positions from the new base if they are needed afterward.
The fragment assumes a valid old allocation, truthful counts, enough replacement storage, and successful allocation. In ordinary throwing new, a failure to allocate raises an exception before the following copy and deletion steps run. More complete ownership designs must also account for exceptions or failures in more complicated element operations. The example shows the storage transition, not a full general-purpose container implementation.
An interior address is for access, not deletion #
In p = values + 2, p refers to the third element of the live allocation, assuming that element exists. Adding two advances by two elements of the pointer's type, rather than by two bytes.[1]
You may use an interior pointer for bounded traversal. Keep the owning base pointer unchanged so cleanup can use the address originally returned by new[]. Do not pass an interior pointer to delete[]. A one-past-end pointer can help mark the stopping position but cannot be dereferenced.
This distinction separates two jobs: ownership retains the allocation's origin, while traversal describes a current position. Moving a traversal pointer does not move the allocation itself or change its length.
Several row allocations make a different shape #
A pointer-to-pointer grid can allocate an outer array of pointers, then a separate integer array for each row. The outer array stores row addresses, not all of the cells contiguously in one built-in two-dimensional array.
int** grid = new int*[rows];
for (int r = 0; r < rows; ++r) grid[r] = new int[columns]{};
// Use grid[r][c] only with valid row and column indexes.
for (int r = 0; r < rows; ++r) delete[] grid[r];
delete[] grid;First, grid obtains room for rows pointer elements. Those row slots are not ready for cell access until the following loop gives each its own array. Each row allocation contains columns zero-initialized integers. Once constructed, grid[r] selects a row pointer and grid[r][c] selects a cell in that row.
For a two-row, three-column instance, construction creates one outer allocation and two inner allocations. Cleanup must therefore release three allocations, not one. Row and column bounds remain independent, and a function receiving the structure must also receive dimensions or row lengths. Pointers do not carry those lengths.
This representation can allow rows with different lengths, but the displayed example deliberately uses the same column count for each. It is not interchangeable with a single contiguous built-in rectangular array. A function must accept the actual representation it will process.
Clean up while the row addresses still exist #
The cleanup loop releases each row using its original address. The final delete[] then releases the outer array of pointer slots. Deleting the outer array first would discard the stored row addresses without automatically deleting the row allocations.
The example shows ordinary completed construction. If a later row allocation fails after earlier ones succeed, already allocated rows and the outer array still require cleanup. Nested vectors or suitable owning objects can arrange automatic cleanup; raw nested allocation requires an explicit partial-construction plan.
Practice and explanation #
After growing an array, should used also become the new capacity? No. Copying three active values into six slots leaves three active values. What happens to a pointer into the old array after deletion? It dangles; it must not be used even if its numerical address looks unchanged.
Why are rows deleted before the outer pointer array? The outer array still holds the addresses needed to release each row. Releasing it does not recursively release allocations reachable through its pointer elements.