Chapter 12. Dynamic Memory
Our programs have used only static or stack memory. Static memory is used for local static objects, for class static data members, and for variables defined outside any function. Stack memory is used for non-static 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, this is for objects that the program allocates at run time.
12.1 Dynamic Memory and Smart Pointers
In C++, dynamic memory is managed through a pair of operators: new, which allocates, and optionally initialises, 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.
To make using dynamic memory easier, 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.
shared_ptr, which allows multiple pointers to refer to the same object.
unique_ptr, which "owns" the object to which it points.
weak_ptr, which 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
A default initialised smart pointer holds a null pointer.
The make_shared
function will allocate and initialise an object in dynamic memory and returns a shared_ptr
that points to that object.
When we copy or assign a shared_ptr
, each shared_ptr
keeps track of how many other shared_ptr
s point to the same object. 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.
The counter is decremented when we assign a new value to the shared_ptr
and when the shared_ptr
itself is destroyed. Once a shared_ptr
's counter goes to zero, the shared_ptr automatically frees the object that it manages.
Operations Common to shared_ptr and unique_ptr
Description
shared_ptr<T> sp;
Null smart pointer that can point to objects of type T.
unique_ptr<T> up;
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.
swap(p, q);
Swaps the pointers in p and q.
p.swap(q);
It does so through another special member function known as a destructor. Analogous to its constructors, each class has a destructor, the destructor controls what happens when objects of that class type are destroyed.
Operations Specific to shared_ptr
Description
make_shared<T>(args);
Returns a shared_ptr pointing to a dynamically allocated object type T.
shared_ptr<T> p(q);
p is a copy of the shared_ptr q.
p = q;
p and q are shared_ptrs holding pointers that can be converted to one another.
p.unique();
Returns true if p.use_count() is one; false otherwise.
p.use_count();
Returns the number of objects sharing with p.
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 elements allocated by a vector exist only while the vector itself exits. When a vector is destroyed, the elements in the vector are also destroyed.
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.
12.1.2 Managing Memory Directly
The new returns a pointer to the object it allocates:
When we provide an initialiser inside parentheses, we can use auto to deduce the type to allocate, we can use auto only with a single initialiser inside parentheses:
It is legal to use new to allocate const object, a dynamically allocated const object must be initialised.
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
. We can prevent new from throwing an exception by using a different form of new:
A delete expression takes a pointer to the object we want to free:
Deleting a pointer a 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.
Functions that return pointers to dynamic memory put a burden on their callers, the caller must remember to delete the 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.
12.1.3 Using shared_ptrs with new
We can also initialise a smart pointer from a pointer returned by new:
The smart pointer constructors that take pointers are explicit.
Other Ways to Define and Change shared_ptrs
Description
shared_ptr<T> p(q);
p manages the object to which the built-in pointer q points.
shared_ptr<T> p(u);
p assumes ownership from the unique_ptr u.
shared_ptr<T> p(q, d);
p assumes ownership for the object to which the built-in pointer q points; p will uses the callable object d in place of delete.
shared_ptr<T> p(p2, d);
p is a copy of the shared_ptr p2; p will uses the callable object d in place of delete.
p.reset();
If p is the only shared_ptr pointing at its object, reset frees p's existing object.
p.reset(q);
p.reset(q, d);
The smart pointer types define a function named get that returns a built-in pointer to the object that the smart pointer is managing.
12.1.4 Smart Pointers and Exception
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 we create a shared_ptr, we can pass an optional argument that points to a deleter function:
12.1.5 unique_ptr
A unique_ptr "owns" the object to which it points. The object to which a unique_ptr
points is destroyed when the unique_ptr
is destroyed.
Because a unique_ptr
owns the object to which it points, unique_ptr
does not support ordinary copy or assignment:
unique_ptr Operations
Description
unique_ptr<T> u1;
Null unique_ptrs that can point to objects of type T.
unique_ptr<T, D> u2;
unique_ptr<T, D> u(d);
Null unique_ptr that point to objects of type T that uses d, which must be an object type D in place of delete.
u = nullptr;
Deletes the object to which u points.
u.release();
Relinquishes control of the pointer u had held; return the pointer u had held and makes u null.
u.reset();
Deletes the object to which u points.
u.reset(q);
If the built-in pointer q is supplied, make u point to that object.
u.reset(nullptr);
Although we can't copy or assign a unique_ptr
, we can transfer ownership from one unique_ptr
to another by calling release or reset:
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:
Overriding the deleter in a unique_ptr affects the unique_ptr type as well as how we construct objects of that type. We must supply the deleter type inside the angle brackets along with the type to which the unique_ptr can point.
12.1.6 weak_ptr
A weak_ptr
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 the shared_ptr
.
week_ptrs
Description
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.
w = p;
p can be a shared_ptr or a weak_ptr.
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, return a null shared_ptr; otherwise returns a shared_ptr to the object to which w points.
12.2 Dynamic Arrays
The library includes a template class named allocator that lets us separate allocation from initialisation.
12.2.1 new and Arrays
The size inside the brackets must have integral type but need not be a constant.
Under the new standard, we can also provide a braced list of element initialisers:
It is legal to dynamically allocate an empty array:
To free a dynamic array, we use a special form of delete that includes an empty pair of square brackets:
The library provides a version of unique_ptr that can manage arrays allocated via new.
unique_ptrs to Arrays
Description
unique_ptr<T[]> u;
u can point to a dynamically allocated array of the type T.
unique_ptr<T[]> u(p);
u points to the dynamically allocated array to which the built-in point p points.
u[i]
Returns the object at position i in the array that u owns.
12.2.2 The allocator Class
The library allocator class, which is defined in the memory
header, lets us separate allocation from construction.
Standard allocator Class and Customised Algorithm
Description
allocator<T> a;
Defines an allocator object named a that can allocate memory for objects of type t.
a.allocate(n);
Allocates raw, unconstructed memory to hold n objects of type T.
a.deallocate(p, n);
Deallocates memory that held n objects of type T starting at the address in the T* pointer p.
a.construct(p, args);
p must be a pointer to type T that points to raw memory; arg are passed to a constructor for type T, which is used to construct an object in the memory pointed to by p.
a.destroy(p);
Runs the destructor on the object pointed to by the T* pointer p.
The memory an allocator allocates is unconstructed.
allocator Algorithms
Description
uninitialised_copy(b, e, b2);
Copies elements from the input range denoted by iterators b and e into unconstructed, raw memory denoted by the iterator b2.
uninitialised_copy_n(b, n, b2);
Copies n elements starting from the one denoted by the iterator b into raw memory starting at b2.
uninitialised_fill(b, e, t);
Constructs objects in the range of raw memory denoted by iterators b and e as a copy of t.
uninitialised_fill_n(b, n, t);
Constructs unsigned number n objects starting at b.
Last updated
Was this helpful?