The language itself defines two operators that allocate and free dynamic memory. The new
operator allocates memory, and delete
frees memory allocated by new
.
For reasons that will become clear as we describe how these operators work, using these operators to manage memory is considerably more error-prone than using a smart pointer. Moreover, classes that do manage their own memory—unlike those that use smart pointers—cannot rely on the default definitions for the members that copy, assign, and destroy class objects (§ 7.1.4, p. 264). As a result, programs that use smart pointers are likely to be easier to write and debug.
Until you have read Chapter 13, your classes should allocate dynamic memory only if they use smart pointers to manage that memory.
new
to Dynamically Allocate and Initialize ObjectsObjects allocated on the free store are unnamed, so new
offers no way to name the objects that it allocates. Instead, new
returns a pointer to the object it allocates:
int *pi = new int; // pi points to a dynamically allocated,
// unnamed, uninitialized int
This new
expression constructs an object of type int
on the free store and returns a pointer to that object.
By default, dynamically allocated objects are default initialized (§ 2.2.1, p. 43), which means that objects of built-in or compound type have undefined value; objects of class type are initialized by their default constructor:
string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized int
We can initialize a dynamically allocated object using direct initialization (§ 3.2.1, p. 84). We can use traditional construction (using parentheses), and under the new standard, we can also use list initialization (with curly braces):
int *pi = new int(1024); // object to which pi points has value 1024
string *ps = new string(10, '9'), // *ps is "9999999999"
// vector with ten elements with values from 0 to 9
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};
We can also value initialize (§ 3.3.1, p. 98) a dynamically allocated object by following the type name with a pair of empty parentheses:
string *ps1 = new string; // default initialized to the empty string
string *ps = new string(); // value initialized to the empty string
int *pi1 = new int; // default initialized; *pi1 is undefined
int *pi2 = new int(); // value initialized to 0; *pi2 is 0
For class types (such as string
) that define their own constructors (§ 7.1.4, p. 262), requesting value initialization is of no consequence; regardless of form, the object is initialized by the default constructor. In the case of built-in types the difference is significant; a value-initialized object of built-in type has a well-defined value but a default-initialized object does not. Similarly, members of built-in type in classes that rely on the synthesized default constructor will also be uninitialized if those members are not initialized in the class body (§ 7.1.4, p. 263).
For the same reasons as we usually initialize variables, it is also a good idea to initialize dynamically allocated objects.
When we provide an initializer inside parentheses, we can use auto
(§ 2.5.2, p. 68) to deduce the type of the object we want to allocate from that initializer. However, because the compiler uses the initializer’s type to deduce the type to allocate, we can use auto
only with a single initializer inside parentheses:
auto p1 = new auto(obj); // p points to an object of the type of obj
// that object is initialized from obj
auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer
The type of p1
is a pointer to the auto
-deduced type of obj
. If obj
is an int
, then p1
is int*;
if obj
is a string
, then p1
is a string*;
and so on. The newly allocated object is initialized from the value of obj
.
const
ObjectsIt is legal to use new
to allocate const
objects:
// allocate and initialize a const int
const int *pci = new const int(1024);
// allocate a default-initialized const empty string
const string *pcs = new const string;
Like any other const
, a dynamically allocated const
object must be initialized. A const
dynamic object of a class type that defines a default constructor (§ 7.1.4, p. 263) may be initialized implicitly. Objects of other types must be explicitly initialized. Because the allocated object is const
, the pointer returned by new
is a pointer to const
(§ 2.4.2, p. 62).
Although modern machines tend to have huge memory capacity, it is always possible that the free store will be exhausted. Once a program has used all of its available memory, new
expressions will fail. By default, if new
is unable to allocate the requested storage, it throws an exception of type bad_alloc
(§ 5.6, p. 193). We can prevent new
from throwing an exception by using a different form of new
:
// if allocation fails, new returns a null pointer
int *p1 = new int; // if allocation fails, new throws std::bad_alloc
int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer
For reasons we’ll explain in § 19.1.2 (p. 824) this form of new
is referred to as placement new. A placement new expression lets us pass additional arguments to new
. In this case, we pass an object named nothrow
that is defined by the library. When we pass nothrow
to new
, we tell new
that it must not throw an exception. If this form of new
is unable to allocate the requested storage, it will return a null pointer. Both bad_alloc
and nothrow
are defined in the new
header.
In order to prevent memory exhaustion, we must return dynamically allocated memory to the system once we are finished using it. We return memory through a delete
expression. A delete
expression takes a pointer to the object we want to free:
delete p; // p must point to a dynamically allocated object or be null
Like new
, a delete
expression performs two actions: It destroys the object to which its given pointer points, and it frees the corresponding memory.
delete
The pointer we pass to delete
must either point to dynamically allocated memory or be a null pointer (§ 2.3.2, p. 53). Deleting a pointer to memory that was not allocated by new
, or deleting the same pointer value more than once, is undefined:
int i, *pi1 = &i, *pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;
delete i; // error: i is not a pointer
delete pi1; // undefined: pi1 refers to a local
delete pd; // ok
delete pd2; // undefined: the memory pointed to by pd2 was already freed
delete pi2; // ok: it is always ok to delete a null pointer
The compiler will generate an error for the delete
of i
because it knows that i
is not a pointer. The errors associated with executing delete
on pi1
and pd2
are more insidious: In general, compilers cannot tell whether a pointer points to a statically or dynamically allocated object. Similarly, the compiler cannot tell whether memory addressed by a pointer has already been freed. Most compilers will accept these delete
expressions, even though they are in error.
Although the value of a const
object cannot be modified, the object itself can be destroyed. As with any other dynamic object, a const
dynamic object is freed by executing delete
on a pointer that points to that object:
const int *pci = new const int(1024);
delete pci; // ok: deletes a const object
As we saw in § 12.1.1 (p. 452), memory that is managed through a shared_ptr
is automatically deleted when the last shared_ptr
is destroyed. The same is not true for memory we manage using built-in pointers. A dynamic object managed through a built-in pointer exists until it is explicitly deleted.
Functions that return pointers (rather than smart pointers) to dynamic memory put a burden on their callers—the caller must remember to delete the memory:
// factory returns a pointer to a dynamically allocated object
Foo* factory(T arg)
{
// process arg as appropriate
return new Foo(arg); // caller is responsible for deleting this memory
}
Like our earlier factory
function (§ 12.1.1, p. 453), this version of factory
allocates an object but does not delete
it. Callers of factory
are responsible for freeing this memory when they no longer need the allocated object. Unfortunately, all too often the caller forgets to do so:
void use_factory(T arg)
{
Foo *p = factory(arg);
// use p but do not delete it
} // p goes out of scope, but the memory to which p points is not freed!
Here, our use_factory
function calls factory
, which allocates a new object of type Foo
. When use_factory
returns, the local variable p
is destroyed. That variable is a built-in pointer, not a smart pointer.
Unlike class types, nothing happens when objects of built-in type are destroyed. In particular, when a pointer goes out of scope, nothing happens to the object to which the pointer points. If that pointer points to dynamic memory, that memory is not automatically freed.
Dynamic memory managed through built-in pointers (rather than smart pointers) exists until it is explicitly freed.
In this example, p
was the only pointer to the memory allocated by factory
. Once use_factory
returns, the program has no way to free that memory. Depending on the logic of our overall program, we should fix this bug by remembering to free the memory inside use_factory
:
void use_factory(T arg)
{
Foo *p = factory(arg);
// use p
delete p; // remember to free the memory now that we no longer need it
}
or, if other code in our system needs to use the object allocated by use_factory
, we should change that function to return a pointer to the memory it allocated:
Foo* use_factory(T arg)
{
Foo *p = factory(arg);
// use p
return p; // caller must delete the memory
}
delete ...
When we delete
a pointer, that pointer becomes invalid. Although the pointer is invalid, on many machines the pointer continues to hold the address of the (freed) dynamic memory. After the delete
, the pointer becomes what is referred to as a dangling pointer. A dangling pointer is one that refers to memory that once held an object but no longer does so.
Dangling pointers have all the problems of uninitialized pointers (§ 2.3.2, p. 54). We can avoid the problems with dangling pointers by deleting the memory associated with a pointer just before the pointer itself goes out of scope. That way there is no chance to use the pointer after the memory associated with the pointer is freed. If we need to keep the pointer around, we can assign nullptr
to the pointer after we use delete
. Doing so makes it clear that the pointer points to no object.
A fundamental problem with dynamic memory is that there can be several pointers that point to the same memory. Resetting the pointer we use to delete
that memory lets us check that particular pointer but has no effect on any of the other pointers that still point at the (freed) memory. For example:
int *p(new int(42)); // p points to dynamic memory
auto q = p; // p and q point to the same memory
delete p; // invalidates both p and q
p = nullptr; // indicates that p is no longer bound to an object
Here both p
and q
point at the same dynamically allocated object. We delete
that memory and set p
to nullptr
, indicating that the pointer no longer points to an object. However, resetting p
has no effect on q
, which became invalid when we deleted the memory to which p
(and q
!) pointed. In real systems, finding all the pointers that point to the same memory is surprisingly difficult.
Exercise 12.6: Write a function that returns a dynamically allocated vector
of int
s. Pass that vector
to another function that reads the standard input to give values to the elements. Pass the vector
to another function to print the values that were read. Remember to delete
the vector
at the appropriate time.
Exercise 12.7: Redo the previous exercise, this time using shared_ptr
.
Exercise 12.8: Explain what if anything is wrong with the following function.
bool b() {
int* p = new int;
// ...
return p;
}
Exercise 12.9: Explain what happens in the following code:
int *q = new int(42), *r = new int(100);
r = q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;