new
and ArraysWe ask new
to allocate an array of objects by specifying the number of objects to allocate in a pair of square brackets after a type name. In this case, new
allocates the requested number of objects and (assuming the allocation succeeds) returns a pointer to the first one:
// call get_size to determine how many ints to allocate
int *pia = new int[get_size()]; // pia points to the first of these ints
The size inside the brackets must have integral type but need not be a constant.
We can also allocate an array by using a type alias (§ 2.5.1, p. 67) to represent an array type. In this case, we omit the brackets:
typedef int arrT[42]; // arrT names the type array of 42 ints
int *p = new arrT; // allocates an array of 42 ints; p points to the first one
Here, new
allocates an array of int
s and returns a pointer to the first one. Even though there are no brackets in our code, the compiler executes this expression using new[]
. That is, the compiler executes this expression as if we had written
int *p = new int[42];
Although it is common to refer to memory allocated by new T[]
as a “dynamic array,” this usage is somewhat misleading. When we use new
to allocate an array, we do not get an object with an array type. Instead, we get a pointer to the element type of the array. Even if we use a type alias to define an array type, new
does not allocate an object of array type. In this case, the fact that we’re allocating an array is not even visible; there is no [
num]. Even so, new
returns a pointer to the element type.
Because the allocated memory does not have an array type, we cannot call begin
or end
(§ 3.5.3, p. 118) on a dynamic array. These functions use the array dimension (which is part of an array’s type) to return pointers to the first and one past the last elements, respectively. For the same reasons, we also cannot use a range for
to process the elements in a (so-called) dynamic array.
By default, objects allocated by new
—whether allocated as a single object or in an array—are default initialized. We can value initialize (§ 3.3.1, p. 98) the elements in an array by following the size with an empty pair of parentheses.
int *pia = new int[10]; // block of ten uninitialized ints
int *pia2 = new int[10](); // block of ten ints value initialized to 0
string *psa = new string[10]; // block of ten empty strings
string *psa2 = new string[10](); // block of ten empty strings
Under the new standard, we can also provide a braced list of element initializers:
// block of ten ints each initialized from the corresponding initializer
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
// block of ten strings; the first four are initialized from the given initializers
// remaining elements are value initialized
string *psa3 = new string[10]{"a", "an", "the", string(3,'x')};
As when we list initialize an object of built-in array type (§ 3.5.1, p. 114), the initializers are used to initialize the first elements in the array. If there are fewer initializers than elements, the remaining elements are value initialized. If there are more initializers than the given size, then the new
expression fails and no storage is allocated. In this case, new
throws an exception of type bad_array_new_length
. Like bad_alloc
, this type is defined in the new
header.
Although we can use empty parentheses to value initialize the elements of an array, we cannot supply an element initializer inside the parentheses. The fact that we cannot supply an initial value inside the parentheses means that we cannot use auto
to allocate an array (§ 12.1.2, p. 459).
We can use an arbitrary expression to determine the number of objects to allocate:
size_t n = get_size(); // get_size returns the number of elements needed
int* p = new int[n]; // allocate an array to hold the elements
for (int* q = p; q != p + n; ++q)
/* process the array */ ;
An interesting question arises: What happens if get_size
returns 0? The answer is that our code works fine. Calling new[n]
with n
equal to 0 is legal even though we cannot create an array variable of size 0:
char arr[0]; // error: cannot define a zero-length array
char *cp = new char[0]; // ok: but cp can't be dereferenced
When we use new
to allocate an array of size zero, new
returns a valid, nonzero pointer. That pointer is guaranteed to be distinct from any other pointer returned by new
. This pointer acts as the off-the-end pointer (§ 3.5.3, p. 119) for a zero-element array. We can use this pointer in ways that we use an off-the-end iterator. The pointer can be compared as in the loop above. We can add zero to (or subtract zero from) such a pointer and can subtract the pointer from itself, yielding zero. The pointer cannot be dereferenced—after all, it points to no element.
In our hypothetical loop, if get_size
returns 0, then n
is also 0. The call to new
will allocate zero objects. The condition in the for
will fail (p
is equal to q + n
because n
is 0). Thus, the loop body is not executed.
To free a dynamic array, we use a special form of delete
that includes an empty pair of square brackets:
delete p; // p must point to a dynamically allocated object or be null
delete [] pa; // pa must point to a dynamically allocated array or be null
The second statement destroys the elements in the array to which pa
points and frees the corresponding memory. Elements in an array are destroyed in reverse order. That is, the last element is destroyed first, then the second to last, and so on.
When we delete
a pointer to an array, the empty bracket pair is essential: It indicates to the compiler that the pointer addresses the first element of an array of objects. If we omit the brackets when we delete
a pointer to an array (or provide them when we delete
a pointer to an object), the behavior is undefined.
Recall that when we use a type alias that defines an array type, we can allocate an array without using []
with new
. Even so, we must use brackets when we delete a pointer to that array:
typedef int arrT[42]; // arrT names the type array of 42 ints
int *p = new arrT; // allocates an array of 42 ints; p points to the first one
delete [] p; // brackets are necessary because we allocated an array
Despite appearances, p
points to the first element of an array of objects, not to a single object of type arrT
. Thus, we must use []
when we delete p
.
The compiler is unlikely to warn us if we forget the brackets when we delete
a pointer to an array or if we use them when we delete
a pointer to an object. Instead, our program is apt to misbehave without warning during execution.
The library provides a version of unique_ptr
that can manage arrays allocated by new
. To use a unique_ptr
to manage a dynamic array, we must include a pair of empty brackets after the object type:
// up points to an array of ten uninitialized ints
unique_ptr<int[]> up(new int[10]);
up.release(); // automatically uses delete[] to destroy its pointer
The brackets in the type specifier (<int[]>)
say that up
points not to an int
but to an array of int
s. Because up
points to an array, when up
destroys the pointer it manages, it will automatically use delete[]
.
unqiue_ptr
s that point to arrays provide slightly different operations than those we used in § 12.1.5 (p. 470). These operations are described in Table 12.6 (overleaf). When a unique_ptr
points to an array, we cannot use the dot and arrow member access operators. After all, the unqiue_ptr
points to an array, not an object so these operators would be meaningless. On the other hand, when a unqiue_ptr
points to an array, we can use the subscript operator to access the elements in the array:
for (size_t i = 0; i != 10; ++i)
up[i] = i; // assign a new value to each of the elements
Unlike unique_ptr
, shared_ptr
s provide no direct support for managing a dynamic array. If we want to use a shared_ptr
to manage a dynamic array, we must provide our own deleter:
// to use a shared_ptr we must supply a deleter
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
sp.reset(); // uses the lambda we supplied that uses delete[] to free the array
Here we pass a lambda (§ 10.3.2, p. 388) that uses delete[]
as the deleter.
Had we neglected to supply a deleter, this code would be undefined. By default, shared_ptr
uses delete
to destroy the object to which it points. If that object is a dynamic array, using delete
has the same kinds of problems that arise if we forget to use []
when we delete a pointer to a dynamic array (§ 12.2.1, p. 479).
The fact that shared_ptr
does not directly support managing arrays affects how we access the elements in the array:
// shared_ptrs don't have subscript operator and don't support pointer arithmetic
for (size_t i = 0; i != 10; ++i)
*(sp.get() + i) = i; // use get to get a built-in pointer
There is no subscript operator for shared_ptr
s, and the smart pointer types do not support pointer arithmetic. As a result, to access the elements in the array, we must use get
to obtain a built-in pointer, which we can then use in normal ways.
Exercise 12.23: Write a program to concatenate two string literals, putting the result in a dynamically allocated array of char
. Write a program to concatenate two library string
s that have the same value as the literals used in the first program.
Exercise 12.24: Write a program that reads a string from the standard input into a dynamically allocated character array. Describe how your program handles varying size inputs. Test your program by giving it a string of data that is longer than the array size you’ve allocated.
Exercise 12.25: Given the following new
expression, how would you delete pa
?
int *pa = new int[10];