C++ lets you allocate objects dynamically. The authors of C++ Primer discuss why properly freeing dynamic memory is a rich source of bugs, and how the new library defines smart pointers—shared_ptr, unique_ptr, and weak_ptr—that make managing dynamic memory much safer.

The programs we’ve written so far have used objects that have well-defined lifetimes. Global objects are allocated at program start-up and destroyed when the program ends. Local, automatic objects are created and destroyed when the block in which they are defined is entered and exited. Local static objects are allocated before their first use and are destroyed when the program ends.

In addition to supporting automatic and static objects, C++ lets us allocate objects dynamically. Dynamically allocated objects have a lifetime that is independent of where they are created; they exist until they are explicitly freed.

Properly freeing dynamic objects turns out to be a surprisingly rich source of bugs. To make using dynamic objects safer, the library defines two smart pointer types that manage dynamically allocated objects. Smart pointers ensure that the objects to which they point are automatically freed when it is appropriate to do so.

Our programs have used only static or stack memory. Static memory is used for local static objects (§ 6.1.1, p. 205), for class static data members (§ 7.6, p. 300), and for variables defined outside any function. Stack memory is used for nonstatic objects defined inside functions. Objects allocated in static or stack memory are automatically created and destroyed by the compiler. Stack objects exist only while the block in which they are defined is executing; static objects are allocated before they are used, and they are destroyed when the program ends.

In addition to static or stack memory, every program also has a pool of memory that it can use. This memory is referred to as the free store or heap. Programs use the heap for objects that they dynamically allocate—that is, for objects that the program allocates at run time. The program controls the lifetime of dynamic objects; our code must explicitly destroy such objects when they are no longer needed.

Warning

Although necessary at times, dynamic memory is notoriously tricky to manage correctly.

12.1. Dynamic Memory and Smart Pointers

In C++, dynamic memory is managed through a pair of operators: new, which allocates, and optionally initializes, an object in dynamic memory and returns a pointer to that object; and delete, which takes a pointer to a dynamic object, destroys that object, and frees the associated memory.

Dynamic memory is problematic because it is surprisingly hard to ensure that we free memory at the right time. Either we forget to free the memory—in which case we have a memory leak—or we free the memory when there are still pointers referring to that memory—in which case we have a pointer that refers to memory that is no longer valid.

To make using dynamic memory easier (and safer), the new library provides two smart pointer types that manage dynamic objects. A smart pointer acts like a regular pointer with the important exception that it automatically deletes the object to which it points. The new library defines two kinds of smart pointers that differ in how they manage their underlying pointers: shared_ptr, which allows multiple pointers to refer to the same object, and unique_ptr, which “owns” the object to which it points. The library also defines a companion class named weak_ptr that is a weak reference to an object managed by a shared_ptr. All three are defined in the memory header.

12.1.1. The shared_ptr Class

Like vectors, smart pointers are templates (§ 3.3, p. 96). Therefore, when we create a smart pointer, we must supply additional information—in this case, the type to which the pointer can point. As with vector, we supply that type inside angle brackets that follow the name of the kind of smart pointer we are defining:

shared_ptr<string> p1; // shared_ptr that can point at a string
shared_ptr<list<int>> p2; // shared_ptr that can point at a list of ints

We use a smart pointer in ways that are similar to using a pointer. Dereferencing a smart pointer returns the object to which the pointer points. When we use a smart pointer in a condition, the effect is to test whether the pointer is null:

// if p1 is not null, check whether it's the empty string
if (p1 && p1->empty())
*p1 = "hi"; // if so, dereference p1 to assign a new value to that string

Table 12.1 (overleaf) lists operations common to shared_ptr and unique_ptr. Those that are particular to shared_ptr are listed in Table 12.2 (p. 453).

Table 12.1. Operations Common to shared_ptr and unique_ptr

shared_ptr<T> spunique_ptr<T> up

Null smart pointer that can point to objects of type T.

p

Use p as a condition; true if p points to an object.

*p

Dereference p to get the object to which p points.

p->mem

Synonym for (*p).mem.

p.get()

Returns the pointer in p. Use with caution; the object to which the returned pointer points will disappear when the smart pointer deletes it.

swap(p, q)p.swap(q)

Swaps the pointers in p and q.

Table 12.2. Operations Specific to shared_ptr

make_shared<T>(args)

Returns a shared_ptr pointing to a dynamically allocated object of type T. Uses args to initialize that object.

shared_ptr<T> p(q)

p is a copy of the shared_ptr q; increments the count in q. The pointer in q must be convertible to T* (§ 4.11.2, p. 161).

p = q

p and q are shared_ptrs holding pointers that can be converted to one another. Decrements p’s reference count and increments q’s count; deletes p’s existing memory if p’s count goes to 0.

p.unique()

Returns true if p.use_count() is one; false otherwise.

p.use_count()

Returns the number of objects sharing with p;may be aslow operation, intended primarily for debugging purposes.

The make_shared Function

The safest way to allocate and use dynamic memory is to call a library function named make_shared. This function allocates and initializes an object in dynamic memory and returns a shared_ptr that points to that object. Like the smart pointers, make_shared is defined in the memory header.

When we call make_shared, we must specify the type of object we want to create. We do so in the same way as we use a template class, by following the function name with a type enclosed in angle brackets:

Like the sequential-container emplace members (§ 9.3.1, p. 345), make_shared uses its arguments to construct an object of the given type. For example, a call to make_shared<string> must pass argument(s) that match one of the string constructors. Calls to make_shared<int> can pass any value we can use to initialize an int. And so on. If we do not pass any arguments, then the object is value initialized (§ 3.3.1, p. 98).

Of course, ordinarily we use auto (§ 2.5.2, p. 68) to make it easier to define an object to hold the result of make_shared:

Copying and Assigning shared_ptrs

When we copy or assign a shared_ptr, each shared_ptr keeps track of how many other shared_ptrs point to the same object:

auto p = make_shared<int>(42); // object to which p points has one user
auto q(p); // p and q point to the same object
// object to which p and q point has two users

We can think of a shared_ptr as if it has an associated counter, usually referred to as a reference count. Whenever we copy a shared_ptr, the count is incremented. For example, the counter associated with a shared_ptr is incremented when we use it to initialize another shared_ptr, when we use it as the right-hand operand of an assignment, or when we pass it to (§ 6.2.1, p. 209) or return it from a function by value (§ 6.3.2, p. 224). The counter is decremented when we assign a new value to the shared_ptr and when the shared_ptr itself is destroyed, such as when a local shared_ptr goes out of scope (§ 6.1.1, p. 204).

Once a shared_ptr’s counter goes to zero, the shared_ptr automatically frees the object that it manages:

auto r = make_shared<int>(42); // int to which r points has one user
r = q; // assign to r, making it point to a different address
// increase the use count for the object to which q points
// reduce the use count of the object to which r had pointed
// the object r had pointed to has no users; that object is automatically freed

Here we allocate an int and store a pointer to that int in r. Next, we assign a new value to r. In this case, r is the only shared_ptr pointing to the one we previously allocated. That int is automatically freed as part of assigning q to r.

NOTE

It is up to the implementation whether to use a counter or another data structure to keep track of how many pointers share state. The key point is that the class keeps track of how many shared_ptrs point to the same object and automatically frees that object when appropriate.

shared_ptrs Automatically Destroy Their Objects ...

When the last shared_ptr pointing to an object is destroyed, the shared_ptr class automatically destroys the object to which that shared_ptr points. It does so through another special member function known as a destructor. Analogous to its constructors, each class has a destructor. Just as a constructor controls initialization, the destructor controls what happens when objects of that class type are destroyed.

Destructors generally free the resources that an object has allocated. For example, the string constructors (and other string members) allocate memory to hold the characters that compose the string. The string destructor frees that memory. Similarly, several vector operations allocate memory to hold the elements in the vector. The destructor for vector destroys those elements and frees the memory used for the elements.

The destructor for shared_ptr decrements the reference count of the object to which that shared_ptr points. If the count goes to zero, the shared_ptr destructor destroys the object to which the shared_ptr points and frees the memory used by that object.

...and Automatically Free the Associated Memory

The fact that the shared_ptr class automatically frees dynamic objects when they are no longer needed makes it fairly easy to use dynamic memory. For example, we might have a function that returns a shared_ptr to a dynamically allocated object of a type named Foo that can be initialized by an argument of type T:

Because factory returns a shared_ptr, we can be sure that the object allocated by factory will be freed when appropriate. For example, the following function stores the shared_ptr returned by factory in a local variable:

Because p is local to use_factory, it is destroyed when use_factory ends (§ 6.1.1, p. 204). When p is destroyed, its reference count is decremented and checked. In this case, p is the only object referring to the memory returned by factory. Because p is about to go away, the object to which p points will be destroyed and the memory in which that object resides will be freed.

The memory will not be freed if there is any other shared_ptr pointing to it:

In this version, the return statement in use_factory returns a copy of p to its caller (§ 6.3.2, p. 224). Copying a shared_ptr adds to the reference count of that object. Now when p is destroyed, there will be another user for the memory to which p points. The shared_ptr class ensures that so long as there are any shared_ptrs attached to that memory, the memory itself will not be freed.

Because memory is not freed until the last shared_ptr goes away, it can be important to be sure that shared_ptrs don’t stay around after they are no longer needed. The program will execute correctly but may waste memory if you neglect to destroy shared_ptrs that the program does not need. One way that shared_ptrs might stay around after you need them is if you put shared_ptrs in a container and subsequently reorder the container so that you don’t need all the elements. You should be sure to erase shared_ptr elements once you no longer need those elements.

NOTE

If you put shared_ptrs in a container, and you subsequently need to use some, but not all, of the elements, remember to erase the elements you no longer need.

Classes with Resources That Have Dynamic Lifetime

Programs tend to use dynamic memory for one of three purposes:

They don’t know how many objects they’ll need

They don’t know the precise type of the objects they need

They want to share data between several objects

The container classes are an example of classes that use dynamic memory for the first purpose and we’ll see examples of the second in Chapter 15. In this section, we’ll define a class that uses dynamic memory in order to let several objects share the same underlying data.

So far, the classes we’ve used allocate resources that exist only as long as the corresponding objects. For example, each vector “owns” its own elements. When we copy a vector, the elements in the original vector and in the copy are separate from one another:

vector<string> v1; // empty vector
{ // new scope
vector<string> v2 = {"a", "an", "the"};
v1 = v2; // copies the elements from v2 into v1
} // v2 is destroyed, which destroys the elements in v2
// v1 has three elements, which are copies of the ones originally in v2

The elements allocated by a vector exist only while the vector itself exists. When a vector is destroyed, the elements in the vector are also destroyed.

Some classes allocate resources with a lifetime that is independent of the original object. As an example, assume we want to define a class named Blob that will hold a collection of elements. Unlike the containers, we want Blob objects that are copies of one another to share the same elements. That is, when we copy a Blob, the original and the copy should refer to the same underlying elements.

In general, when two objects share the same underlying data, we can’t unilaterally destroy the data when an object of that type goes away:

Blob<string> b1; // empty Blob
{ // new scope
Blob<string> b2 = {"a", "an", "the"};
b1 = b2; // b1 and b2 share the same elements
} // b2 is destroyed, but the elements in b2 must not be destroyed
// b1 points to the elements originally created in b2

In this example, b1 and b2 share the same elements. When b2 goes out of scope, those elements must stay around, because b1 is still using them.

NOTE

One common reason to use dynamic memory is to allow multiple objects to share the same state.

Defining the StrBlob Class

Ultimately, we’ll implement our Blob class as a template, but we won’t learn how to do so until § 16.1.2 (p. 658). For now, we’ll define a version of our class that can manage strings. As a result, we’ll name this version of our class StrBlob.

The easiest way to implement a new collection type is to use one of the library containers to manage the elements. That way, we can let the library type manage the storage for the elements themselves. In this case, we’ll use a vector to hold our elements.

However, we can’t store the vector directly in a Blob object. Members of an object are destroyed when the object itself is destroyed. For example, assume that b1 and b2 are two Blobs that share the same vector. If that vector were stored in one of those Blobs—say, b2—then that vector, and therefore its elements, would no longer exist once b2 goes out of scope. To ensure that the elements continue to exist, we’ll store the vector in dynamic memory.

To implement the sharing we want, we’ll give each StrBlob a shared_ptr to a dynamically allocated vector. That shared_ptr member will keep track of how many StrBlobs share the same vector and will delete the vector when the last StrBlob using that vector is destroyed.

We still need to decide what operations our class will provide. For now, we’ll implement a small subset of the vector operations. We’ll also change the operations that access elements (e.g., front and back): In our class, these operations will throw an exception if a user attempts to access an element that doesn’t exist.

Our class will have a default constructor and a constructor that has a parameter of type initializer_list<string> (§ 6.2.6, p. 220). This constructor will take a braced list of initializers.

Inside the class we implemented the size, empty, and push_back members. These members forward their work through the data pointer to the underlying vector. For example, size() on a StrBlob calls data->size(), and so on.

StrBlob Constructors

Each constructor uses its constructor initializer list (§ 7.1.4, p. 265) to initialize its data member to point to a dynamically allocated vector. The default constructor allocates an empty vector:

The constructor that takes an initializer_list passes its parameter to the corresponding vector constructor (§ 2.2.1, p. 43). That constructor initializes the vector’s elements by copying the values in the list.

Element Access Members

The pop_back, front, and back operations access members in the vector. These operations must check that an element exists before attempting to access that element. Because several members need to do the same checking, we’ve given our class a private utility function named check that verifies that a given index is in range. In addition to an index, check takes a string argument that it will pass to the exception handler. The string describes what went wrong:

The front and back members should be overloaded on const (§ 7.3.2, p. 276). Defining those versions is left as an exercise.

Copying, Assigning, and Destroying StrBlobs

Like our Sales_data class, StrBlob uses the default versions of the operations that copy, assign, and destroy objects of its type (§ 7.1.5, p. 267). By default, these operations copy, assign, and destroy the data members of the class. Our StrBlob has only one data member, which is a shared_ptr. Therefore, when we copy, assign, or destroy a StrBlob, its shared_ptr member will be copied, assigned, or destroyed.

As we’ve seen, copying a shared_ptr increments its reference count; assigning one shared_ptr to another increments the count of the right-hand operand and decrements the count in the left-hand operand; and destroying a shared_ptr decrements the count. If the count in a shared_ptr goes to zero, the object to which that shared_ptr points is automatically destroyed. Thus, the vector allocated by the StrBlob constructors will be automatically destroyed when the last StrBlob pointing to that vector is destroyed.

Exercises Section 12.1.1

Exercise 12.1: How many elements do b1 and b2 have at the end of this code?

Exercise 12.2: Write your own version of the StrBlob class including the const versions of front and back.

Exercise 12.3: Does this class need const versions of push_back and pop_back? If so, add them. If not, why aren’t they needed?

Exercise 12.4: In our check function we didn’t check whether i was greater than zero. Why is it okay to omit that check?

Exercise 12.5: We did not make the constructor that takes an initializer_list explicit (§ 7.5.4, p. 296). Discuss the pros and cons of this design choice.

12.1.2. Managing Memory Directly

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.

Warning

Until you have read Chapter 13, your classes should allocate dynamic memory only if they use smart pointers to manage that memory.

Using new to Dynamically Allocate and Initialize Objects

Objects 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:

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:

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):

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).

Best Practices

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.

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).

Memory Exhaustion

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:

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.

Freeing Dynamic Memory

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 deleteexpression. 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.

Pointer Values and 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:

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:

Dynamically Allocated Objects Exist until They Are Freed

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:

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.

Warning

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:

There are three common problems with using new and delete to manage dynamic memory:

Forgetting to delete memory. Neglecting to delete dynamic memory is known as a “memory leak,” because the memory is never returned to the free store. Testing for memory leaks is difficult because they usually cannot be detected until the application is run for a long enough time to actually exhaust memory.

Using an object after it has been deleted. This error can sometimes be detected by making the pointer null after the delete.

Deleting the same memory twice. This error can happen when two pointers address the same dynamically allocated object. If delete is applied to one of the pointers, then the object’s memory is returned to the free store. If we subsequently delete the second pointer, then the free store may be corrupted.

These kinds of errors are considerably easier to make than they are to find and fix.

Best Practices

You can avoid all of these problems by using smart pointers exclusively. The smart pointer will take care of deleting the memory only when there are no remaining smart pointers pointing to that memory.

Resetting the Value of a Pointer after a 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.

...Provides Only Limited Protection

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:

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.

Exercises Section 12.1.2

Exercise 12.6: Write a function that returns a dynamically allocated vector of ints. 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.

12.1.3. Using shared_ptrs with new

As we’ve seen, if we do not initialize a smart pointer, it is initialized as a null pointer. As described in Table 12.3, we can also initialize a smart pointer from a pointer returned by new:

shared_ptr<double> p1; // shared_ptr that can point at a double
shared_ptr<int> p2(new int(42)); // p2 points to an int with value 42

Table 12.3. Other Ways to Define and Change shared_ptrs

shared_ptr<T> p(q)

p manages the object to which the built-in pointer q points; q must point to memory allocated by new and must be convertible to T*.

shared_ptr<T> p(u)

p assumes ownership from the unique_ptr u; makes u null.

shared_ptr<T> p(q, d)

p assumes ownership for the object to which the built-in pointer q points. q must be convertible to T* (§ 4.11.2, p. 161). p will use the callable object d (§ 10.3.2, p. 388) in place of delete to free q.

shared_ptr<T> p(p2, d)

p is a copy of the shared_ptr p2 as described in Table 12.2 except that p usesthe callable object d in place of delete.

p.reset()p.reset(q)p.reset(q, d)

If p is the only shared_ptr pointing at its object, reset frees p’s existing object. If the optional built-in pointer q is passed, makes p point to q, otherwise makes p null. If d is supplied, will call d to free q otherwise uses delete to free q.

The smart pointer constructors that take pointers are explicit (§ 7.5.4, p. 296). Hence, we cannot implicitly convert a built-in pointer to a smart pointer; we must use the direct form of initialization (§ 3.2.1, p. 84) to initialize a smart pointer:

The initialization of p1 implicitly asks the compiler to create a shared_ptr from the int* returned by new. Because we can’t implicitly convert a pointer to a smart pointer, this initialization is an error. For the same reason, a function that returns a shared_ptr cannot implicitly convert a plain pointer in its return statement:

By default, a pointer used to initialize a smart pointer must point to dynamic memory because, by default, smart pointers use delete to free the associated object. We can bind smart pointers to pointers to other kinds of resources. However, to do so, we must supply our own operation to use in place of delete. We’ll see how to supply our own deletion code in § 12.1.4 (p. 468).

Don’t Mix Ordinary Pointers and Smart Pointers ...

A shared_ptr can coordinate destruction only with other shared_ptrs that are copies of itself. Indeed, this fact is one of the reasons we recommend using make_shared rather than new. That way, we bind a shared_ptr to the object at the same time that we allocate it. There is no way to inadvertently bind the same memory to more than one independently created shared_ptr.

Consider the following function that operates on a shared_ptr:

// ptr is created and initialized when process is called
void process(shared_ptr<int> ptr)
{
// use ptr
} // ptr goes out of scope and is destroyed

The parameter to process is passed by value, so the argument to process is copied into ptr. Copying a shared_ptr increments its reference count. Thus, inside process the count is at least 2. When process completes, the reference count of ptr is decremented but cannot go to zero. Therefore, when the local variable ptr is destroyed, the memory to which ptr points will not be deleted. The right way to use this function is to pass it a shared_ptr:

In this call, we passed a temporary shared_ptr to process. That temporary is destroyed when the expression in which the call appears finishes. Destroying the temporary decrements the reference count, which goes to zero. The memory to which the temporary points is freed when the temporary is destroyed.

But x continues to point to that (freed) memory; x is now a dangling pointer. Attempting to use the value of x is undefined.

When we bind a shared_ptr to a plain pointer, we give responsibility for that memory to that shared_ptr. Once we give shared_ptr responsibility for a pointer, we should no longer use a built-in pointer to access the memory to which the shared_ptr now points.

Warning

It is dangerous to use a built-in pointer to access an object owned by a smart pointer, because we may not know when that object is destroyed.

...and Don’t Use get to Initialize or Assign Another Smart Pointer

The smart pointer types define a function named get (described in Table 12.1 (p. 452)) that returns a built-in pointer to the object that the smart pointer is managing. This function is intended for cases when we need to pass a built-in pointer to code that can’t use a smart pointer. The code that uses the return from get must not delete that pointer.

Although the compiler will not complain, it is an error to bind another smart pointer to the pointer returned by get:

shared_ptr<int> p(new int(42)); // reference count is 1
int *q = p.get(); // ok: but don't use q in any way that might delete its pointer
{ // new block
// undefined: two independent shared_ptrs point to the same memory
shared_ptr<int>(q);
} // block ends, q is destroyed, and the memory to which q points is freed
int foo = *p; // undefined; the memory to which p points was freed

In this case, both p and q point to the same memory. Because they were created independently from each other, each has a reference count of 1. When the block in which q was defined ends, q is destroyed. Destroying q frees the memory to which q points. That makes p into a dangling pointer, meaning that what happens when we attempt to use p is undefined. Moreover, when p is destroyed, the pointer to that memory will be deleted a second time.

Warning

Use get only to pass access to the pointer to code that you know will not delete the pointer. In particular, never use get to initialize or assign to another smart pointer.

Other shared_ptr Operations

The shared_ptr class gives us a few other operations, which are listed in Table 12.2 (p. 453) and Table 12.3 (on the previous page). We can use reset to assign a new pointer to a shared_ptr:

p = new int(1024); // error: cannot assign a pointer to a shared_ptr
p.reset(new int(1024)); // ok: p points to a new object

Like assignment, reset updates the reference counts and, if appropriate, deletes the object to which p points. The reset member is often used together with unique to control changes to the object shared among several shared_ptrs. Before changing the underlying object, we check whether we’re the only user. If not, we make a new copy before making the change:

if (!p.unique())
p.reset(new string(*p)); // we aren't alone; allocate anew copy
*p += newVal; // now that we know we're the only pointer, okay to change this object

Exercises Section 12.1.3

Exercise 12.10: Explain whether the following call to the process function defined on page 464 is correct. If not, how would you correct the call?

shared_ptr<int> p(new int(42));
process(shared_ptr<int>(p));

Exercise 12.11: What would happen if we called process as follows?

process(shared_ptr<int>(p.get()));

Exercise 12.12: Using the declarations of p and sp explain each of the following calls to process. If the call is legal, explain what it does. If the call is illegal, explain why:

auto p = new int();
auto sp = make_shared<int>();

(a)process(sp);

(b)process(new int());

(c)process(p);

(d)process(shared_ptr<int>(p));

Exercise 12.13: What happens if we execute the following code?

auto sp = make_shared<int>();
auto p = sp.get();
delete p;

12.1.4. Smart Pointers and Exceptions

In § 5.6.2 (p. 196) we noted that programs that use exception handling to continue processing after an exception occurs need to ensure that resources are properly freed if an exception occurs. One easy way to make sure resources are freed is to use smart pointers.

When we use a smart pointer, the smart pointer class ensures that memory is freed when it is no longer needed even if the block is exited prematurely:

When a function is exited, whether through normal processing or due to an exception, all the local objects are destroyed. In this case, sp is a shared_ptr, so destroying sp checks its reference count. Here, sp is the only pointer to the memory it manages; that memory will be freed as part of destroying sp.

In contrast, memory that we manage directly is not automatically freed when an exception occurs. If we use built-in pointers to manage memory and an exception occurs after a new but before the corresponding delete, then that memory won’t be freed:

If an exception happens between the new and the delete, and is not caught inside f, then this memory can never be freed. There is no pointer to this memory outside the function f. Thus, there is no way to free this memory.

Smart Pointers and Dumb Classes

Many C++ classes, including all the library classes, define destructors (§ 12.1.1, p. 452) that take care of cleaning up the resources used by that object. However, not all classes are so well behaved. In particular, classes that are designed to be used by both C and C++ generally require the user to specifically free any resources that are used.

Classes that allocate resources—and that do not define destructors to free those resources—can be subject to the same kind of errors that arise when we use dynamic memory. It is easy to forget to release the resource. Similarly, if an exception happens between when the resource is allocated and when it is freed, the program will leak that resource.

We can often use the same kinds of techniques we use to manage dynamic memory to manage classes that do not have well-behaved destructors. For example, imagine we’re using a network library that is used by both C and C++. Programs that use this library might contain code such as

struct destination; // represents what we are connecting to
struct connection; // information needed to use the connection
connection connect(destination*); // open the connection
void disconnect(connection); // close the given connection
void f(destination &d /* other parameters */)
{
// get a connection; must remember to close it when done
connection c = connect(&d);
// use the connection
// if we forget to call disconnect before exiting f, there will be no way to close c
}

If connection had a destructor, that destructor would automatically close the connection when f completes. However, connection does not have a destructor. This problem is nearly identical to our previous program that used a shared_ptr to avoid memory leaks. It turns out that we can also use a shared_ptr to ensure that the connection is properly closed.

Using Our Own Deletion Code

By default, shared_ptrs assume that they point to dynamic memory. Hence, by default, when a shared_ptr is destroyed, it executes delete on the pointer it holds. To use a shared_ptr to manage a connection, we must first define a function to use in place of delete. It must be possible to call this deleter function with the pointer stored inside the shared_ptr. In this case, our deleter must take a single argument of type connection*:

void end_connection(connection *p) { disconnect(*p); }

When we create a shared_ptr, we can pass an optional argument that points to a deleter function (§ 6.7, p. 247):

void f(destination &d /* other parameters */)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}

When p is destroyed, it won’t execute delete on its stored pointer. Instead, p will call end_connection on that pointer. In turn, end_connection will call disconnect, thus ensuring that the connection is closed. If f exits normally, then p will be destroyed as part of the return. Moreover, p will also be destroyed, and the connection will be closed, if an exception occurs.

Caution: Smart Pointer Pitfalls

Smart pointers can provide safety and convenience for handling dynamically allocated memory only when they are used properly. To use smart pointers correctly, we must adhere to a set of conventions:

Don’t use the same built-in pointer value to initialize (or reset) more than one smart pointer.

Don’t delete the pointer returned from get().

Don’t use get() to initialize or reset another smart pointer.

If you use a pointer returned by get(), remember that the pointer will become invalid when the last corresponding smart pointer goes away.

If you use a smart pointer to manage a resource other than memory allocated by new, remember to pass a deleter (§ 12.1.4, p. 468, and § 12.1.5, p. 471).

Exercises Section 12.1.4

Exercise 12.14: Write your own version of a function that uses a shared_ptr to manage a connection.

Exercise 12.15: Rewrite the first exercise to use a lambda (§ 10.3.2, p. 388) in place of the end_connection function.

12.1.5. unique_ptr

A unique_ptr “owns” the object to which it points. Unlike shared_ptr, only one unique_ptr at a time can point to a given object. The object to which a unique_ptr points is destroyed when the unique_ptr is destroyed. Table 12.4 lists the operations specific to unique_ptrs. The operations common to both were covered in Table 12.1 (p. 452).

Table 12.4. unique_ptr Operations (See Also Table 12.1 (p. 452))

unique_ptr<T> u1unique_ptr<T, D> u2

Null unique_ptrs that can point to objects of type T. u1 will use delete to free its pointer; u2 will use a callable object of type D to free its pointer.

unique_ptr<T, D> u(d)

Null unique_ptr that point to objects of type T that uses d, which mustbe an objectof type D in place of delete.

u = nullptr

Deletes the object to which u points; makes u null.

u.release()

Relinquishes control of the pointer u had held; returns the pointer u had held and makes u null.

u.reset()u.reset(q)u.reset(nullptr)

Deletes the object to which u points;If the built-in pointer q is supplied, makes u point to that object.Otherwise makes u null.

Unlike shared_ptr, there is no library function comparable to make_shared that returns a unique_ptr. Instead, when we define a unique_ptr, we bind it to a pointer returned by new. As with shared_ptrs, we must use the direct form of initialization:

unique_ptr<double> p1; // unique_ptr that can point at a double
unique_ptr<int> p2(new int(42)); // p2 points to int with value 42

Because a unique_ptr owns the object to which it points, unique_ptr does not support ordinary copy or assignment:

The release member returns the pointer currently stored in the unique_ptr and makes that unique_ptr null. Thus, p2 is initialized from the pointer value that had been stored in p1 and p1 becomes null.

The reset member takes an optional pointer and repositions the unique_ptr to point to the given pointer. If the unique_ptr is not null, then the object to which the unique_ptr had pointed is deleted. The call to reset on p2, therefore, frees the memory used by the string initialized from "Stegosaurus", transfers p3’s pointer to p2, and makes p3 null.

Calling release breaks the connection between a unique_ptr and the object it had been managing. Often the pointer returned by release is used to initialize or assign another smart pointer. In that case, responsibility for managing the memory is simply transferred from one smart pointer to another. However, if we do not use another smart pointer to hold the pointer returned from release, our program takes over responsibility for freeing that resource:

p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer
auto p = p2.release(); // ok, but we must remember to delete(p)

Passing and Returning unique_ptrs

There is one exception to the rule that we cannot copy a unique_ptr: We can copy or assign a unique_ptr that is about to be destroyed. The most common example is when we return a unique_ptr from a function:

In both cases, the compiler knows that the object being returned is about to be destroyed. In such cases, the compiler does a special kind of “copy” which we’ll discuss in § 13.6.2 (p. 534).

Backward Compatibility: auto_ptr

Earlier versions of the library included a class named auto_ptr that had some, but not all, of the properties of unique_ptr. In particular, it was not possible to store an auto_ptr in a container, nor could we return one from a function.

Although auto_ptr is still part of the standard library, programs should use unique_ptr instead.

Passing a Deleter to unique_ptr

Like shared_ptr, by default, unique_ptr uses delete to free the object to which a unique_ptr points. As with shared_ptr, we can override the default deleter in a unique_ptr (§ 12.1.4, p. 468). However, for reasons we’ll describe in § 16.1.6 (p. 676), the way unique_ptr manages its deleter is differs from the way shared_ptr does.

Overridding the deleter in a unique_ptr affects the unique_ptr type as well as how we construct (or reset) objects of that type. Similar to overriding the comparison operation of an associative container (§ 11.2.2, p. 425), we must supply the deleter type inside the angle brackets along with the type to which the unique_ptr can point. We supply a callable object of the specified type when we create or reset an object of this type:

// p points to an object of type objT and uses an object of type delT to free that object
// it will call an object named fcn of type delT
unique_ptr<objT, delT> p (new objT, fcn);

As a somewhat more concrete example, we’ll rewrite our connection program to use a unique_ptr in place of a shared_ptr as follows:

void f(destination &d /* other needed parameters */)
{
connection c = connect(&d); // open the connection
// when p is destroyed, the connection will be closed
unique_ptr<connection, decltype(end_connection)*>
p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}

Here we use decltype (§ 2.5.3, p. 70) to specify the function pointer type. Because decltype(end_connection) returns a function type, we must remember to add a * to indicate that we’re using a pointer to that type (§ 6.7, p. 250).

Exercises Section 12.1.5

Exercise 12.16: Compilers don’t always give easy-to-understand error messages if we attempt to copy or assign a unique_ptr. Write a program that contains these errors to see how your compiler diagnoses them.

Exercise 12.17: Which of the following unique_ptr declarations are illegal or likely to result in subsequent program error? Explain what the problem is with each one.

12.1.6. weak_ptr

A weak_ptr (Table 12.5) is a smart pointer that does not control the lifetime of the object to which it points. Instead, a weak_ptr points to an object that is managed by a shared_ptr. Binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr. Once the last shared_ptr pointing to the object goes away, the object itself will be deleted. That object will be deleted even if there are weak_ptrs pointing to it—hence the name weak_ptr, which captures the idea that a weak_ptr shares its object “weakly.”

Table 12.5. weak_ptrs

weak_ptr<T> w

Null weak_ptr that can point at objects of type T.

weak_ptr<T> w(sp)

weak_ptr that points to the same object as the shared_ptr sp. T must be convertible to the type to which sp points.

w = p

p can be a shared_ptr or a weak_ptr. After the assignment w shares ownership with p.

w.reset()

Makes w null.

w.use_count()

The number of shared_ptrs that share ownership with w.

w.expired()

Returns true if w.use_count() is zero, false otherwise.

w.lock()

If expired is true, returns a null shared_ptr; otherwise returns a shared_ptr to the object to which w points.

Here both wp and p point to the same object. Because the sharing is weak, creating wp doesn’t change the reference count of p; it is possible that the object to which wp points might be deleted.

Because the object might no longer exist, we cannot use a weak_ptr to access its object directly. To access that object, we must call lock. The lock function checks whether the object to which the weak_ptr points still exists. If so, lock returns a shared_ptr to the shared object. As with any other shared_ptr, we are guaranteed that the underlying object to which that shared_ptr points continues to exist at least as long as that shared_ptr exists. For example:

Here we enter the body of the if only if the call to lock succeeds. Inside the if, it is safe to use np to access that object.

Checked Pointer Class

As an illustration of when a weak_ptr is useful, we’ll define a companion pointer class for our StrBlob class. Our pointer class, which we’ll name StrBlobPtr, will store a weak_ptr to the data member of the StrBlob from which it was initialized. By using a weak_ptr, we don’t affect the lifetime of the vector to which a given StrBlob points. However, we can prevent the user from attempting to access a vector that no longer exists.

StrBlobPtr will have two data members: wptr, which is either null or points to a vector in a StrBlob; and curr, which is the index of the element that this object currently denotes. Like its companion StrBlob class, our pointer class has a check member to verify that it is safe to dereference the StrBlobPtr:

The default constructor generates a null StrBlobPtr. Its constructor initializer list (§ 7.1.4, p. 265) explicitly initializes curr to zero and implicitly initializes wptr as a null weak_ptr. The second constructor takes a reference to StrBlob and an optional index value. This constructor initializes wptr to point to the vector in the shared_ptr of the given StrBlob object and initializes curr to the value of sz. We use a default argument (§ 6.5.1, p. 236) to initialize curr to denote the first element by default. As we’ll see, the sz parameter will be used by the end member of StrBlob.

It is worth noting that we cannot bind a StrBlobPtr to a const StrBlob object. This restriction follows from the fact that the constructor takes a reference to a nonconst object of type StrBlob.

The check member of StrBlobPtr differs from the one in StrBlob because it must check whether the vector to which it points is still around:

Because a weak_ptr does not participate in the reference count of its corresponding shared_ptr, the vector to which this StrBlobPtr points might have been deleted. If the vector is gone, lock will return a null pointer. In this case, any reference to the vector will fail, so we throw an exception. Otherwise, check verifies its given index. If that value is okay, check returns the shared_ptr it obtained from lock.

Pointer Operations

We’ll learn how to define our own operators in Chapter 14. For now, we’ve defined functions named deref and incr to dereference and increment the StrBlobPtr, respectively.

The deref member calls check to verify that it is safe to use the vector and that curr is in range:

If check succeeds, p is a shared_ptr to the vector to which this StrBlobPtr points. The expression (*p)[curr] dereferences that shared_ptr to get the vector and uses the subscript operator to fetch and return the element at curr.

The incr member also calls check:

// prefix: return a reference to the incremented object
StrBlobPtr& StrBlobPtr::incr()
{
// if curr already points past the end of the container, can't increment it
check(curr, "increment past end of StrBlobPtr");
++curr; // advance the current state
return *this;
}

Of course, in order to access the data member, our pointer class will have to be a friend of StrBlob (§ 7.3.4, p. 279). We’ll also give our StrBlob class begin and end operations that return a StrBlobPtr pointing to itself: