Memory and Resources - The Standard Library - The C++ Programming Language (2013)

The C++ Programming Language (2013)

Part IV: The Standard Library

34. Memory and Resources

Anyone can have an idea; it’s what you do with the idea that’s the important thing.

– Terry Pratchett


“Almost Containers”

array; bitset; vector<bool>; Tuples

Resource Management Pointers

unique_ptr; shared_ptr; weak_ptr


The Default Allocator; Allocator Traits; Pointer Traits; Scoped Allocators

The Garbage Collection Interface

Uninitialized Memory

Temporary Buffers; raw_storage_iterator


34.1. Introduction

The STL (Chapter 31, Chapter 32, Chapter 33) is the most highly structured and general part standard-library facilities for the management and manipulation of data. This chapter presents facilities that are more specialized or deal with raw memory (as opposed to typed objects).

34.2. “Almost Containers”

The standard library provides several containers that don’t fit perfectly into the STL framework (§31.4, §32.2, §33.1). Examples are built-in arrays, array, and string. I sometimes refer to those as “almost containers” (§31.4), but that is not quite fair: they hold elements, so they are containers, but each has restrictions or added facilities that make them awkward in the context of the STL. Describing them separately also simplifies the description of the STL.


Why does the standard library provide so many containers? They serve common but different (often overlapping) needs. If the standard library didn’t provide them, many people would have to design and implement their own. For example:

pair and tuple are heterogeneous; all other containers are homogeneous (all elements are of the same type).

array, vector, and tuple elements are contiguously allocated; forward_list and map are linked structures.

bitset and vector<bool> hold bits and access them through proxy objects; all other standard-library containers can hold a variety of types and access elements directly.

basic_string requires its elements to be some form of character and to provide string manipulation, such as concatenation and locale-sensitive operations (Chapter 39) and valarray requires its elements to be numbers and to provide numerical operations.

All of these containers can be seen as providing specialized services needed by large communities of programmers. No single container could serve all of these needs because some needs are contradictory, for example, “ability to grow” vs. “guaranteed to be allocated in a fixed location,” and “elements do not move when elements are added” vs. “contiguously allocated.” Furthermore, a very general container would imply overhead deemed unacceptable for individual containers.

34.2.1. array

An array, defined in <array>, is a fixed-size sequence of elements of a given type where the number of elements is specified at compile time. Thus, an array can be allocated with its elements on the stack, in an object, or in static storage. The elements are allocated in the scope where thearray is defined. An array is best understood as a built-in array with its size firmly attached, without implicit, potentially surprising conversions to pointer types, and with a few convenience functions provided. There is no overhead (time or space) involved in using an array compared to using a built-in array. An array does not follow the “handle to elements” model of STL containers. Instead, an array directly contains its elements:

template<typename T, size_t N> // an array of N Ts (§iso.23.3.2)
struct array {

types and operations like vector's (§31.4),
except operations that change the container size, constructors, and assign() functions

void fill(const T& v);//
assign N copies of v
void swap(array&) noexcept(noexcept(swap(declval<T&>(), declval<T&>())));

T __elem[N]; // implementation detail

No “management information” (e.g., a size) is stored in an array. This implies that moving (§17.5) an array is no more efficient than copying it (unless the array’s elements are resource handles with efficient moves). An array does not have a constructor or an allocator (because it does not directly allocate anything).

The number of elements and subscript values for array are of an unsigned type (size_t), just like vector’s, but different from the built-in array’s. So, array<int,–1> might be accepted by an inattentive compiler. Hope for a warning.

An array can be initialized by an initializer list:

array<int,3> a1 = { 1, 2, 3 };

The number of elements in the initializer must be equal to or less than the number of elements specified for the array. As usual, if the initializer list provides values for some but not all elements, the remainder is initialized with the appropriate default value. For example:

void f()
array<string, 4> aa = {"Churchill", "Clare"};

The last two elements will be empty strings.

The element count is not optional:

array<int> ax = { 1, 2, 3 }; // error size not specified

To save us from a special case, the number of elements can be zero:

int<int,0> a0;

The element count must be a constant expression:

void f(int n)
array<string,n> aa = {"John's", "Queens' "}; //
error: size not a constant expression

If you need the element count to be variable, use vector. On the other hand, since array’s element count is known at compile time, array’s size() is a constexpr function.

There is no constructor for array that copies an argument value (as there is for vector; §31.3.2). Instead, a fill() operation is provided:

void f()
array<int,8> aa; //
uninitialized, so far
aa.fill(99); // assign eight copies of 99
// ...

Because an array doesn’t follow the “handle to elements” model, swap() has to actually swap elements so that swapping two array<T,N>s applies swap() to N pairs of Ts. The declaration of array<T,N>::swap() basically says that if a swap() of Ts can throw, then so can a swap() of anarray<T,N>. Obviously, throwing swap()s should be avoided like the plague.

When necessary, an array can be explicitly passed to a C-style function that expects a pointer. For example:

void f(int* p, int sz); // C-style interface

void g()
array<int,10> a;

f(a,a.size()); // error: no conversion
f(&a[0],a.size()); // C-style use
f(,a.size()); // C-style use

auto p = find(a.begin(),a.end(),777); // C++/STL-style use
// ...

Why would we use an array when vector is so much more flexible? Because an array is less flexible, it is simpler. Occasionally, there is a significant performance advantage to be had by directly accessing elements allocated on the stack rather than allocating elements on the free store, accessing them indirectly through the vector (a handle), and then deallocating them. On the other hand, the stack is a limited resource (especially on some embedded systems), and stack overflow is nasty.

Why would we use an array when we could use a built-in array? An array knows its size, so it is easy to use with standard-library algorithms, and it can be copied (using = or initialization). However, my main reason to prefer array is that it saves me from surprising nasty conversions to pointers. Consider:

void h()

Circle a1[10];
array<Circle,10> a2;
Shape* p1 = a1; // OK: disaster waiting to happen
Shape* p2 = a2; // error: no conversion of array<Circle,10> to Shape*
p1[3].draw(); // disaster

The “disaster” comment assumes that sizeof(Shape)<sizeof(Circle), so that subscripting a Circle[] through a Shape* gives a wrong offset (§27.2.1, § All standard containers provide this advantage over built-in arrays.

An array can be seen as a tuple34.2.4) where all elements are of the same type. The standard library provides support for that view. The tuple helper type functions tuple_size and tuple_element can be applied to arrays:

tuple_size<array<T,N>>::value // N
tuple_element<S,array<T,N>>::type // T

We can also use a get<i> function to access the ith element:

template<size_t index, typename T, size_t N>
T& get(array<T,N>& a) noexcept;
template<size_t index, typename T, size_t N>
T&& get(array<T,N>&& a) noexcept;
template<size_t index, typename T, size_t N>
const T& get(const array<T,N>& a) noexcept;

For example:

array<int,7> a = {1,2,3,5,8,13,25};
auto x1 = get<5>(a); //
auto x2 = a[5]; // 13
auto sz = tuple_size<decltype(a)>::value; // 7
typename tuple_element<5,decltype(a)>::type x3 = 13; // x3 is an int

These type functions are for people writing code that expects tuples.

Use a constexpr function (§28.2.2) and a type alias (§28.2.1) to improve readability:

auto sz = Tuple_size<decltype(a)>(); // 7

Tuple_element<5,decltype(a)> x3 = 13; // x3 is an int

The tuple syntax is meant for use in generic code.

34.2.2. bitset

Aspects of a system, such as the state of an input stream (§, are often represented as a set of flags indicating binary conditions such as good/bad, true/false, and on/off. C++ supports the notion of small sets of flags efficiently through bitwise operations on integers (§11.1.1). Classbitset<N> generalizes this notion and offers greater convenience by providing operations on a sequence of N bits [0:N), where N is known at compile time. For sets of bits that don’t fit into a long long int, using a bitset is much more convenient than using integers directly. For smaller sets,bitset is usually optimized. If you want to name the bits, rather than numbering them, the alternatives are to use a set31.4.3), an enumeration (§8.4), or a bit-field (§8.2.7).

A bitset<N> is an array of N bits. It is presented in <bitset>. A bitset differs from a vector<bool>34.2.3) by being of fixed size, from set31.4.3) by having its bits indexed by integers rather than associatively by value, and from both vector<bool> and set by providing operations to manipulate the bits.

It is not possible to address a single bit directly using a built-in pointer (§7.2). Consequently, bitset provides a reference-to-bit (proxy) type. This is actually a generally useful technique for addressing objects for which a built-in pointer for some reason is unsuitable:

template<size_t N>
class bitset {
class reference { //
reference to a single bit:
friend class bitset;
reference() noexcept;
public: //
support zero-based subscripting in [0:b.size())
~reference() noexcept;
reference& operator=(bool x) noexcept; //
for b[i] = x;
reference& operator=(const reference&) noexcept; // for b[i] = b[j];
bool operator~() const noexcept; // return ~b[i]
operator bool() const noexcept; // for x = b[i];
reference& flip() noexcept; // b[i].flip();

For historical reasons, bitset differs in style from other standard-library classes. For example, if an index (also known as a bit position) is out of range, an out_of_range exception is thrown. No iterators are provided. Bit positions are numbered from right to left in the same way bits often are in a word, so the value of b[i] is pow(2,i). Thus, a bitset can be thought of as an N-bit binary number:

Image Constructors

A bitset can be constructed with a specified number of zeros, from the bits in an unsigned long long int, or from a string:



The position npos is string<C>’s “beyond the end” position, meaning “all characters until the end” (§36.3).

When an unsigned long long int argument is supplied, each bit in the integer is used to initialize the corresponding bit in the bitset (if any). A basic_string36.3) argument does the same, except that the character '0' gives the bit value 0, the character '1' gives the bit value 1, and other characters cause an invalid_argument exception to be thrown. For example:

void f()
bitset<10> b1; //
all 0

bitset<16> b2 = 0xaaaa; // 1010101010101010
bitset<32> b3 = 0xaaaa; // 00000000000000001010101010101010

bitset<10> b4 {"1010101010"}; // 1010101010
bitset<10> b5 {"10110111011110",4}; // 0111011110

bitset<10> b6 {string{"1010101010"}}; // 1010101010
bitset<10> b7 {string{"10110111011110"},4}; // 0111011110
bitset<10> b8 {string{"10110111011110"},2,8}; // 0011011101

bitset<10> b9 {string{"n0g00d"}}; // invalid_argument thrown
bitset<10> b10 = string{"101001"}; // error: no implicit string to bitset conversion

A key idea in the design of bitset is that an optimized implementation can be provided for bitsets that fit in a single word. The interface reflects this assumption. bitset Operations

A bitset provides the operators for accessing individual bits and for manipulating all bits in the set:



The >> and << are I/O operators when their first operand is an iostream; othewise, they are shift operators and their second operand must be an integer. For example:

bitset<9> bs ("110001111"};
cout << bs << '\n'; //
write "110001111" to cout
auto bs2 = bs<<3; // bs2 == "001111000";
cout << bs2 << '\n'; // write "001111000" to cout
cin >> bs; // read from cin
bs2 = bs>>3; // bs2 == "000110001" if the input were "110001111"
cout << bs2 << '\n'; // write "000110001" to cout

When bits are shifted, a logical (rather than cyclic) shift is used. That implies that some bits “fall off the end” and that some positions get the default value 0. Note that because size_t is an unsigned type, it is not possible to shift by a negative number. It does, however, imply that b<<–1 shifts by a very large positive value, thus leaving every bit of the bitset b with the value 0. Your compiler should warn against this.

A bitset also supports common operations such as size(), ==, I/O, etc.:



The operations to_ullong() and to_string() provide the inverse operations to the constructors. To avoid nonobvious conversions, named operations were preferred over conversion operators. If the value of the bitset has so many significant bits that it cannot be represented as an unsigned long, to_ulong() throws overflow_error; so does to_ullong() if its bitset argument doesn’t fit.

Fortunately, the template arguments for the basic_string returned by to_string are defaulted. For example, we could write out the binary representation of an int:

void binary(int i)
bitset<8*sizeof(int)> b = i; //
assume 8-bit byte (see also §40.2)

cout << b.to_string<char,char_traits<char>,allocator<char>>() << '\n'; // general and verbose
cout << b.to_string<char>() << '\n'; // use default traits and allocator
cout << b.to_string<>() << '\n'; // use all defaults
cout << b.to_string() << '\n'; // use all defaults

This prints the bits represented as 1s and 0s from left to right, with the most significant bit leftmost, so that argument 123 would give the output


For this example, it is simpler to directly use the bitset output operator:

void binary2(int i)
bitset<8*sizeof(int)> b = i; //
assume 8-bit byte (see also §40.2)
cout << b << '\n';

34.2.3. vector<bool>

The vector<bool> from <vector> is a specialization of vector31.4) providing compact storage of bits (bools):

template<typename A>
class vector<bool,A> { //
specialization of vector<T,A> (§31.4)
using const_reference = bool;
using value_type = bool;
like vector<T,A>

class reference { // support zero-based subscripting in [0:v.size())
friend class vector;
reference() noexcept;

operator bool() const noexcept;
reference& operator=(const bool x) noexcept; //
v[i] = x
reference& operator=(const reference& x) noexcept; // v[i] = v[j]
void flip() noexcept; // flip the bit: v[i]=~v[i]

void flip() noexcept; // flip all bits of v

// ...

The similarity to bitset is obvious, but, unlike bitset but like vector<T>, vector<bool> has an allocator and can have its size changed.

As in a vector<T>, elements of a vector<bool> with higher indices have higher addresses:


This is exactly the opposite of the layout in a bitset. Also, there is no direct support for converting integers and strings to and from a vector<bool>.

Use vector<bool> as you would any other vector<T>, but expect operations on a single bit to be less efficient than the equivalent operations on a vector<char>. Also, it is impossible in C++ to completely faithfully mimic the behavior of a (built-in) reference with a proxy, so don’t try to be subtle about rvalue/lvalue distinctions when using a vector<bool>.

34.2.4. Tuples

The standard library provides two ways of grouping values of arbitrary types into a single object:

• A pair34.2.4.1) holds two values.

• A tuple34.2.4) holds zero or more values.

We use pair when it is useful to know (statically) that we have exactly two values. With tuple, we always have to deal with all possible numbers of values. pair

In <utility>, the standard library provides class pair for manipulating pairs of values:

template<typename T, typename U>
struct pair {
using first_type = T; //
the type of the first element
using second_type = U; // the type of the second element

T first; // first element
U second; // second element

// ...


An operation on pair is noexcept if the corresponding operations on its elements are. Similarly, copy or move operations exist for a pair if the corresponding operations on its elements do.

The elements first and second are members that we can directly read and write. For example:

void f()
pair<string,int> p {"Cambridge",1209};
cout << p.first; //
print "Cambridge"
p.second += 800; // update year
// ...

The piecewise_construct is the name of an object of type piecewise_construct_t used to distinguish between constructing a pair with members of tuple types and constructing a pair using tuples as argument lists for its first and second. For example:

struct Univ {
Univ(const string& n, int r) : name{n}, rank{r} { }
string name;
int rank;
string city = "unknown";

using Tup = tuple<string,int>;
Tup t1 {"Columbia",11}; //
U.S. News 2012
Tup t2 {"Cambridge",2};

pair<Tub,Tub> p1 {t1,t2}; // pair of tuples
pair<Univ,Univ> p2 {piecewise_construct,t1,t2}; // pair of Univs

That is, p1.second is t2, that is, {"Cambridge",2}. To contrast, p2.second is Univ{t2}, that is, {"Cambridge",2,"unknown"}.


The make_pair function avoids explicit mention of the element types of a pair. For example:

auto p = make_pair("Harvard",1736); tuple

In <tuple>, the standard library provides class tuple and various supporting facilities. A tuple is a sequence of N elements of arbitrary types:

template<typename ... Types>
class tuple {

// ...

The number of elements is zero or positive.

For details of tuple design, implementation, and use, see §28.5 and §28.6.4.



The types of tuple and operands of = and arguments to swap(), etc., are not required to be the same. An operation is valid if (and only if) the implied operations on elements are valid. For example, we can assign one tuple to another if each element of the assigned tuple can be assigned to the target element. For example:

tuple<string,vector<double>,int> t2 = make_tuple("Hello, tuples!",vector<int>{1,2,3},'x');

An operation is noexcept if all the element operations are, and an operation throws only if a member operation throws. Similarly, a tuple operation is constexpr if the element operations are.

The number of elements in each tuple of a pair of operands (or arguments) must be the same.

Note that the general tuple constructor is explicit. In particular, this does not work:

tuple<int,int,int> rotate(tuple<int,int,int> t)
return {t.get<2>(),t.get<0>(),t.get<1>()}; //
error: explicit tuple constructor

auto t2 = rotate({3,7,9}); // error: explicit tuple constructor

If all you need is two elements, you can use pair:

pair<int,int> rotate(pair<int,int> p)
return {p.second,p.first};

auto p2 = rotate({3,7});

For more examples, see §28.6.4.



For example, tie() can be used to extract elements from a tuple:

auto t = make_tuple(2.71828,299792458,"Hannibal");
double c;
string name;
tie(c,ignore,name) = t; //
c=299792458; name="Hannibal"

The name ignore refers to an object of a type that ignores assignments. Thus, an ignore in a tie() implies that attempts to assign to its tuple position are ignored. An alternative would be:

double c = get<0>(t); // c=299792458
string name = get<2>(t); // name="Hannibal"

Obviously, this would be more interesting if the tuple came from “elsewhere” so that we didn’t trivially know the element values. For example:

tuple<int,double,string> compute();
double c;
string name;
tie(c,ignore,name) = t; //
results in c and name

34.3. Resource Management Pointers

A pointer points to an object (or not). However, a pointer does not indicate who (if anyone) owns the objects. That is, looking just at a pointer, we have no idea who is supposed to delete the object pointed to, or how, or if at all. In <memory>, we find “smart pointers” to express ownership:

unique_ptr34.3.1) to represent exclusive ownership

shared_ptr34.3.2) to represent shared ownership

weak_ptr34.3.3) to break loops in circular shared data structures

These resource handles are introduced in §5.2.1.

34.3.1. unique_ptr

The unique_ptr (defined in <memory>) provides a semantics of strict ownership:

• A unique_ptr owns the object to which it holds a pointer. That is, it is the unique_ptr’s obligation to destroy the object pointed to (if any) by its contained pointer.

• A unique_ptr cannot be copied (has no copy constructor or copy assignment). However, it can be moved.

• A unique_ptr stores a pointer and deletes the object pointed to (if any) using the associated deleter (if any) when it is itself destroyed (such as when a thread of control leaves the unique_ptr’s scope; §17.2.2).

The uses of unique_ptr include:

• Providing exception safety for dynamically allocated memory (§5.2.1, §13.3)

• Passing ownership of dynamically allocated memory to a function

• Returning dynamically allocated memory from a function

• Storing pointers in containers

Think of unique_ptr as being represented by a simple pointer (“the contained pointer”) or (if it has a deleter) as a pair of pointers:


When a unique_ptr is destroyed, its deleter is called to destroy the owned object. The deleter represents what it means to destroy an object. For example:

• A deleter for a local variable should do nothing.

• A deleter for a memory pool should return the object to the memory pool and destroy it or not, depending on how that pool is defined.

• The default (“no deleter”) version of unique_ptr uses delete. It doesn’t even store the default deleter. It can be a specialization or rely on the empty-base optimization (§28.5).

This way unique_ptr supports general resource management (§5.2).

template<typename T, typename D = default_delete<T>>
class unique_ptr {
using pointer = ptr; //
type of the contained pointer;
// ptr is D::pointer if that is defined, otherwise T*
using element_type = T;
using deleter_type = D;

// ...

The contained pointer is not directly accessible to users.


Note: unique_ptr does not offer a copy constructor or copy assignment. Had it done so, the meaning of “ownership” would have been very hard to define and/or use. If you feel the need for copies, consider using a shared_ptr34.3.2).

It is possible to have a unique_ptr for a built-in array. For example:

unique_ptr<int[]> make_sequence(int n)
unique_ptr p {new int[n]};
for (int i=0; i<n; ++i)
return p;

This is provided as a specialization:

template<typename T, typename D>
class unique_ptr<T[],D> { //
specialization for arrays (§iso.
// the default D=default_delete<T> comes from the general unique_ptr
... like the unique_ptr for individual objects, but with [] instead of * and -> ...

To avoid slicing (§, a Derived[] is not accepted as an argument to a unique_ptr<Base[]> even if Base is a public base of Derived. For example:

class Shape {

class Circle : public Base {
// ...

unique_ptr<Shape> ps {new Circle{p,20}}; // OK
unique_ptr<Shape[]> pa {new Circle[] {Circle{p,20}, Circle{p2,40}}; // error

How can we best think of a unique_ptr? What are the best ways to use a unique_ptr? It is called a pointer (_ptr) and I pronounce it “unique pointer,” but clearly it is not just an ordinary pointer (or there would be no point in defining it). Consider a simple technical example:

unique_ptr<int> f(unique_ptr<int> p)

return p;

void f2(const unique_ptr<int>& p)


void use()
unique_ptr<int> p {new int{7}};
p=f(p); //
error: no copy constructor
p=f(move(p)); // transfer ownership there and back
f2(p); // pass a reference

The f2() body is slightly shorter than f() and f2() is simpler to call, but I find f() easier to think about. The style illustrated by f() is explicit about ownership (and the use of unique_ptr is typically motivated by ownership issues). See also the discussion of the use of non-const references in §7.7.1. On balance, a notation f(x) that modifies x is more error-prone than a y=f(x) notation that does not.

It is a fair estimate that the call of f2() is one or two machine instructions faster than a call of f() (because of the need to place a nullptr in the original unique_ptr), but that is unlikely to be significant. On the other hand, access to the contained pointer involves an extra indirection in f2()compared to f(). This, too, is unlikely to be significant in most programs, so the choice between the styles of f() and f2() has to be made on reasoning about code quality.

Here is a simple example of a deleter used to provide guaranteed release of data obtained from a C program fragment using malloc()43.5):

extern "C" char* get_data(const char* data); // get data from C program fragment

using PtoCF = void(*)(void*);

void test()
unique_ptr<char,PtoCF> p {get_data("my_data"),free};
... use *p ...
} // implicit free(p)

Currently, there is no standard-library make_unique() similar to make_pair() and make_shared()34.3.2). However, it is easily defined:

template<typename T, typename ... Args>
unique_ptr<T> make_unique(Args&&... args) //
default deleter version
return unique_ptr<T>{new T{args...}};

34.3.2. shared_ptr

A shared_ptr represents shared ownership. It is used where two pieces of code need access to some data but neither has exclusive ownership (in the sense of being responsible for destroying the object). A shared_ptr is a kind of counted pointer where the object pointed to is deleted when the use count goes to zero. Think of a shared pointer as a structure with two pointers: one to the object and one to the use count:


The deleter is what is used to delete the shared object when the use count goes to zero. The default deleter is the usual delete (invoke the destructor, if any, and deallocate free store).

For example, consider a Node in a general graph used by an algorithm that adds and removes both nodes and connections between nodes (edges). Obviously, to avoid resource leaks, a Node must be deleted if and only if no other node refers to it. We could try:

struct Node {
vector<Node*> edges;

Given that, answering questions such as “How many nodes points to this node?” is very hard and requires much added “housekeeping” code. We could plug in a garbage collector (§34.5), but that could have negative performance implications if the graph was only a small part of a large application data space. Worse, if the container contained non-memory resources, such as thread handles, file handles, locks, etc., even a garbage collector would leak resources.

Instead, we can use a shared_ptr:

struct Node {
vector<shared_ptr<Node>> edges;
thread worker;

Here, Node’s destructor (the implicitly generated destructor will do fine) deletes its edges. That is, the destructor for each edges[i] is invoked, and the Node pointed to (if any) is deleted if edges[i] was the last pointer to it.

Don’t use a shared_ptr just to pass a pointer from one owner to another; that’s what unique_ptr is for, and unique_ptr does it better and more cheaply. If you have been using counted pointers as return values from factory functions (§21.2.4) and the like, consider upgrading tounique_ptr rather than shared_ptr.

Do not thoughtlessly replace pointers with shared_ptrs in an attempt to prevent memory leaks; shared_ptrs are not a panacea nor are they without costs:

• A circular linked structure of shared_ptrs can cause a resource leak. You need some logical complication to break the circle, for example, use a weak_ptr34.3.3).

• Objects with shared ownership tend to stay “live” for longer than scoped objects (thus causing higher average resource usage).

• Shared pointers in a multi-threaded environment can be expensive (because of the need to prevent data races on the use count).

• A destructor for a shared object does not execute at a predictable time, so the algorithms/logic for the update of any shared object are easier to get wrong than for an object that’s not shared. For example, which locks are set at the time of the destructor’s execution? Which files are open? In general, which objects are “live” and in appropriate states at the (unpredictable) point of execution?

• If a single (last) node keeps a large data structure alive, the cascade of destructor calls triggered by its deletion can cause a significant “garbage collection delay.” That can be detrimental to real-time response.

A shared_ptr represents shared ownership and can be very useful, even essential, but shared ownership isn’t my ideal, and it always carries a cost (independently of how you represent the sharing). It is better (simpler) if an object has a definite owner and a definite, predictable life span. When there is a choice:

• Prefer unique_ptr to shared_ptr.

• Prefer ordinary scoped objects to objects on the heap owned by a unique_ptr.

The shared_ptr provides a fairly conventional set of operations:


In addition, the standard library provides a few helper functions:



For example:

struct S {
int i;
string s;
double d;

auto p = make_shared<S>(1,"Ankh Morpork",4.65);

Now, p is a shared_ptr<S> pointing to an object of type S allocated on the free store, containing {1,string{"Ankh Morpork"},4.65}.

Note that unlike unique_ptr::get_deleter(), shared_ptr’s deleter is not a member function.

34.3.3. weak_ptr

A weak_ptr refers to an object managed by a shared_ptr. To access the object, a weak_ptr can be converted to a shared_ptr using the member function lock(). A weak_ptr allows access to an object, owned by someone else, that

• You need access to (only) if it exists

• May get deleted (by someone else) at any time

• Must have its destructor called after its last use (usually to delete a non-memory resource)

In particular, we use weak pointers to break loops in data structures managed using shared_ptrs.

Think of a weak_ptr as a structure with two pointers: one to the (potentially shared) object and one to the use count structure of that object’s shared_ptrs:


The “weak use count” is needed to keep the use count structure alive because there may be weak_ptrs after the last shared_ptr for an object (and the object) is destroyed.

template<typename T>
class weak_ptr {
using element_type = T;

A weak_ptr has to be converted to a shared_ptr to access “its” object, so it provides relatively few operations:


Consider an implementation of the old “asteroid game.” All asteroids are owned by “the game,” but each asteroid must keep track of neighboring asteroids and handle collisions. A collision typically leads to the destruction of one or more asteroids. Each asteroid must keep a list of other asteroids in its neighborhood. Note that being on such a neighbor list should not keep an asteroid “alive” (so a shared_ptr would be inappropriate). On the other hand, an asteroid must not be destroyed while another asteroid is looking at it (e.g., to calculate the effect of a collision). And obviously, an asteroid destructor must be called to release resources (such as a connection to the graphics system). What we need is a list of asteroids that might still be intact and a way of “grabbing onto one” for a while. A weak_ptr does just that:

void owner()

// ...
vector<shared_ptr<Asteroid>> va(100);
for (int i=0; i<va.siz e(); ++i) {

// ... calculate neighbors for new asteroid ...
va[i].reset(new Asteroid(weak_ptr<Asteroid>(va[neighbor]));

Obviously, I radically simplified “the owner” and gave each new Asteroid just one neighbor. The key is that we give the Asteroid a weak_ptr to that neighbor. The owner keeps a shared_ptr to represent the ownership that’s shared whenever an Asteroid is looking (but not otherwise). The collision calculation for an Asteroid will look something like this:

void collision(weak_ptr<Asteroid> p)
if (auto q = p.lock()) { //
p.lock returns a shared_ptr to p's object
// ... that Asteroid still existed: calculate ...
else { //
Oops: that Asteroid has already been destroyed

Note that even if a user decides to shut down the game and deletes all Asteroids (by destroying the shared_ptrs representing ownership), every Asteroid that is in the middle of calculating a collision still finishes correctly: after the p.lock(), it holds a shared_ptr that will not become invalid.

34.4. Allocators

The STL containers (§31.4) and string (Chapter 36) are resource handles that acquire and release memory to hold their elements. To do so, they use allocators. The basic purpose of an allocator is to provide a source of memory for a given type and a place to return that memory to once it is no longer needed. Thus, the basic allocator functions are:

p=a.allocate(n); // acquire space for n objects of type T
a.deallocate(p,n); // release space for n objects of type T pointed to by p

For example:

template<typename T>
struct Simple_alloc { //
use new[] and delete[] to allocate and deallocate bytes
using value_type = T;

Simple_alloc() {}

T* allocate(size_t n)
{ return reinterpret_cast<T*>(new char[n*sizeof(T)]); }
void deallocate(T* p, size_t n)

{ delete[] reinterpret_cast<char*>(p); }

// ...

Simple_alloc happens to be the simplest standards-conforming allocator. Note the casts to and from char*: allocate() does not invoke constructors and deallocate() does not invoke destructors; they deal in memory, not typed objects.

I can build my own allocator to allocate from an arbitrary area of memory:

class Arena {
void* p;
int s;

Arena(void* pp, int ss); // allocate from p[]

template<typename T>
struct My_alloc { //
use an Arena to allocate and deallocate bytes
Arena& a;
My_alloc(Arena& aa) : a(aa) { }
My_alloc() {}
usual allocator stuff

Once Arenas are made, objects can be constructed in the memory allocated:

constexpr int sz {100000};
Arena my_arena1{new char[sz],sz};
Arena my_arena2{new char[10*sz],10*sz};

vector<int> v0; // allocate using default allocator

vector<int,My_alloc<int>> v1 {My_alloc<int>{my_arena1}}; // construct in my_arena1

vector<int,My_alloc<int>> v2 {My_alloc<int>{my_arena2}}; // construct in my_arena2

vector<int,Simple_alloc<int>> v3; // construct on free store

Typically, the verbosity would be alleviated by the use of aliases. For example:

template<typename T>
using Arena_vec = std::vector<T,My_alloc<T>>;
template<typename T>
using Simple_vec = std::vector<T,Simple_alloc<T>>;

My_alloc<int> Alloc2 {my_arena2}; // named allocator object

Arena_vec<complex<double>> vcd {{{1,2}, {3,4}}, Alloc2}; // explicit allocator
Simple_vec<string> vs {"Sam Vimes", "Fred Colon", "Nobby Nobbs"}; // default allocator

An allocator imposes space overhead in a container only if its objects actually have state (like My_alloc). This is usually achieved by relying on the empty-base optimization (§28.5).

34.4.1. The Default Allocator

A default allocator that allocates using new and deallocates using delete is used (by default) by all standard-library containers.

template<typename T>
class allocator {
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using value_type = T;

template<typename U>
struct rebind { using other = allocator<U>; };

allocator() noexcept;
allocator(const allocator&) noexcept;
template<typename U>
allocator(const allocator<U>&) noexcept;

pointer address(reference x) const noexcept;
const_pointer address(const_reference x) const noexcept;

pointer allocate(size_type n, allocator<void>::const_pointer hint = 0); // allocate n bytes
void deallocate(pointer p, size_type n); // deallocate n bytes

size_type max_size() const noexcept;

template<typename U, typename... Args>
void construct(U* p, Args&&... args); //
new(p) U{args}
template<typename U>
void destroy(U* p); //

The curious rebind template is an archaic alias. It should have been:

template<typename U>
using other = allocator<U>;

However, allocator was defined before such aliases were supported by C++. It is provided to allow an allocator to allocate objects of arbitrary type. Consider:

using Link_alloc = typename A::template rebind<Link>::other;

If A is an allocator, then rebind<Link>::other is an alias for allocator<Link>. For example:

template<typename T, typename A = allocator<T>>
class list {
class Link { /*
... */ };

using Link_alloc = typename A:: template rebind<Link>::other; // allocator<Link>

Link_alloc a; // link allocator
A alloc; // list allocator
// ...

A more restricted specialization of allocator<T> is provided:

class allocator<void> {
typedef void* pointer;
typedef const void* const_pointer;
typedef void value_type;
template<typename U> struct rebind { typedef allocator<U> other; };

This allows us to avoid special cases: We can mention allocator<void> as long as we don’t dereference its pointers.

34.4.2. Allocator Traits

The allocators are “wired together” using allocator_traits. A property of an allocator, say, its pointer type, is found in its trait: allocator_traits<X>::pointer. As usual, the traits technique is used so that I can build an allocator for a type that does not have member types matching the requirements of an allocator, such as int, and a type designed without any thought of allocators.

Basically, allocator_traits provide defaults for the usual set of type aliases and allocator functions. Compared to the default allocator34.4.1), address() is missing and select_on_container_copy_construction() is added:

template<typename A> // §iso.20.6.8
struct allocator_traits {
using allocator_type = A;
using value_type = A::value_type;
using pointer = value_type; //
using const_pointer = Pointer_traits<pointer>::rebind<const value_type>; // trick
using void_pointer = Pointer_traits<pointer>::rebind<void>; // trick
using const_void_pointer = Pointer_traits<pointer>::rebind<const void>; // trick
using difference_type = Pointer_traits<pointer>::difference_type; // trick
using size_type = Make_unsigned<difference_type>; // trick
using propagate_on_container_copy_assignment = false_type; // trick
using propagate_on_container_move_assignment = false_type; // trick
using propagate_on_container_swap = false_type; // trick

template<typename T> using rebind_alloc = A<T,Args>; // trick
template<typename T> using rebind_traits = Allocator_traits<rebind_alloc<T>>;

static pointer allocate(A& a, size_type n) { return a.allocate(n); } // trick
static pointer allocate(A& a, size_type n, const_void_pointer hint) // trick
{ return a.allocate(n,hint); }
static void deallocate(A& a, pointer p, size_type n) { a.deallocate(p, n); } //

template<typename T, typename ... Args>
static void construct(A& a, T* p, Args&&... args) //
{ ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...); }
template<typename T>
static void destroy(A& a, T* p) { p–>T(); } //

static size_type max_size(const A& a) // trick
{ return numeric_limits<size_type>::max() }
static A select_on_container_copy_construction(const A& rhs) { return a; } //

The “trick” is to use the equivalent member of the allocator A if it exists; otherwise, the default specified here. For allocate(n,hint), A::allocate(n) will be called if A has no allocate() taking a hint. The Args are any type arguments needed by A.

I am no fan of trickery in the definition of the standard library, but liberal use of enable_if()28.4) allows this to be implemented in C++.

To make the declarations readable, I assume a few type aliases.

34.4.3. Pointer Traits

An allocator uses pointer_traits to determine properties of pointers and proxy types for pointers:

template<typename P> // §iso.20.6.3
struct pointer_traits {
using pointer = P;
using element_type = T; //
using difference_type = ptrdiff_t; // trick
template<typename U>
using rebind = T*; //

static pointer pointer_to(a); // trick

template<typename T>
struct pointer_traits<T*>{
using pointer = T*;
using element_type = T;
using difference_type = ptrdiff_t;
template<typename U>
using rebind = U*;

static pointer pointer_to(x) noexcept { return addressof(x); }

The “trick” is the same as used by allocator_traits34.4.2): to use the equivalent member of the pointer P if it exists; otherwise, the default specified here. To use the T, the template argument P must be the first argument of a Ptr<T,args> template.

This specification does violence to the C++ language.

34.4.4. Scoped Allocators

A rather sneaky problem can occur when using containers and user-defined allocators: Should an element be in the same allocation area as its container? For example, if you use Your_allocator for Your_string to allocate its elements and I use My_allocator to allocate elements ofMy_vector then which allocator should be used for string elements in My_vector<Your_allocator>>?


The solution is the ability to tell a container which allocator to pass to elements. The key to that is the class scoped_allocator, which provides the mechanism to keep track of an outer allocator (to be used for elements) and an inner allocator (to be passed to elements for their use):

template<typename OuterA, typename... InnerA> // §iso.20.12.1
class scoped_allocator_adaptor : public OuterA {
using Tr = allocator_traits<OuterA>;
using outer_allocator_type = OuterA;
using inner_allocator_type = see below;

using value_type = typename Tr::value_type;
using size_type = typename Tr::size_type;
using difference_type = typename Tr::difference_type;
using pointer = typename Tr::pointer;
using const_pointer = typename Tr::const_pointer;
using void_pointer = typename Tr::void_pointer;
using const_void_pointer = typename Tr::const_void_pointer;
using propagate_on_container_copy_assignment = /*
see §iso.20.12.2 */;
using propagate_on_container_move_assignment = /*
see §iso.20.12.2 */;
using propagate_on_container_swap = /*
see §iso.20.12.2 */;

// ...

We have four alternatives for allocation of vectors of strings:

// vector and string use their own (the default) allocator:
using svec0 = vector<string>;
svec0 v0;

// vector (only) uses My_alloc and string uses its own allocator (the default):
using Svec1 = vector<string,My_alloc<string>>;
Svec1 v1 {My_alloc<string>{my_arena1}};

// vector and string use My_alloc (as above):
using Xstring = basic_string<char,char_traits<char>, My_alloc<char>>;
using Svec2 = vector<Xstring,scoped_allocator_adaptor<My_alloc<Xstring>>>;
Svec2 v2 {scoped_allocator_adaptor<My_alloc<Xstring>>{my_arena1}};

// vector uses its own alloctor (the default) and string uses My_alloc:
using Xstring2 = basic_string<char, char_traits<char>, My_alloc<char>>;
using Svec3 = vector<xstring2,scoped_allocator_adaptor<My_alloc<xstring>,My_alloc<char>>>;
Svec3 v3 {scoped_allocator_adaptor<My_alloc<xstring2>,My_alloc<char>>{my_arena1}};

Obviously, the first variant, Svec0, will be by far the most common, but for systems with serious memory-related performance constraints, the other versions (especially Svec2) can be important. A few more aliases would make that code a bit more readable, but it is good that this is not the kind of code you have to write every day.

The definition of scoped_allocator_adaptor is somewhat involved, but basically it is an allocator much like the default allocator34.4.1) that also keeps track of its “inner” allocator to be passed along for use by contained containers, such as string:



34.5. The Garbage Collection Interface

Garbage collection (automatic recycling of unreferenced regions of memory) is sometimes presented as a panacea. It is not. In particular, resources that are not pure memory can be leaked by a garbage collector. Examples are file handles, thread handles, and locks. I see garbage collection as a convenient last resort after the usual techniques for preventing leaks have been exhausted:

[1] Use resource handles with the proper semantics for an application whenever possible. The standard library provides string, vector, unordered_map, thread, lock_guard, and more. Move semantics allow such objects to be efficiently returned from a function.

[2] Use unique_ptrs to hold on to objects that do not implicitly manage their own resources (such as pointers), need to be protected from premature deletion (because they don’t have proper destructors), or need to be allocated in ways that require special attention (deleters).

[3] Use shared_ptrs to hold objects that require shared ownership.

If consistently used, this sequence of techniques ensures the absence of leaks (i.e., no garbage collection is needed because no garbage is generated). However, in a very large number of real-world programs these techniques (all based on RAII; §13.3) are not consistently used and cannot be easily applied because they involve huge amounts of code structured in different ways. These “different ways” often involve complicated pointer uses, naked news and deletes, explicit type conversions that obscure resource ownership, and similar error-prone low-level techniques. In such cases, a garbage collector is an appropriate last resort. It can reclaim/recycle memory, even if it cannot handle non-memory resources. Don’t even think of using general “finalizers” called at collection time to try to deal with non-memory resources. A garbage collector can sometimes extend the running time for leaking systems (even systems that leak non-memory resources) significantly. For example, a garbage collector might extend the time between resource exhaustion from hours to days for a system that is taken down every night for maintenance. Also, a garbage collector can be instrumented to find the sources of leaks.

It is worth remembering that a garbage-collected system can have its own variants of leaks. For example, if we put a pointer to an object into a hash table and forget its key, the object is de facto leaked. Similarly, a resource referred to by an infinite thread can live forever, even if the thread wasn’t meant to be infinite (e.g., it could be waiting for input that never arrives). Sometimes, resources “living” for an excessive time can be as bad for a system as a permanent leak.

From this basic philosophy it follows that garbage collection is optional in C++. A garbage collector will not be invoked unless explicitly installed and activated. A garbage collector is not even a required part of a standard C++ implementation, but good free and commercial collectors are available. C++ provides a definition of what a garbage collector can do if one is used and an ABI (Application Binary Interface) to help control its actions.

The rules for pointers and lifetimes are expressed in terms of safely-derived pointers (§iso. A safely-derived pointer is (roughly) “a pointer to something allocated by new or to a subobject thereof.” Here are some examples of pointers that are not safely derived, also known asdisguised pointers. Make a pointer point “elsewhere” for a while:

int* p = new int[100];
... collector may run here ...
p –=10;
*p = 10; // can we be sure that the int is still there?

Hide the pointer in an int:

int* p = new int;
int x = reinterpret_cast<int>(p); //
not even portable
p = nullptr;
... collector may run here ...
p = reinterpret_cast<int*>(x);
*p = 10; // can we be sure that the int is still there?

Write a pointer to a file and read it back later:

int* p = new int;
cout << p;
p = nullptr;
... collector may run here ...
cin >> p;
*p = 10; //
can we be sure that the int is still there?

Use the “xor trick” to compress doubly-linked lists:

using Link = pair<Value,long>;

long xor(Link* pre, Link* suc)
static_assert(sizeof(Link*)<=sizeof(long),"a long is smaller than a pointer");
return long{pre}^long{suc};

void insert_between(Value val, Link* pre, Link* suc)

Link* p = new Link{val,xor(pre,suc)};
pre–>second = xor(xor(pre–>second,suc),p);
suc–>second = xor(p,xor(suc–>second,pre));

Using that trick, no undisguised pointers to links are stored.

Don’t use such tricks in a program you want to be considered well behaved and comprehensible to ordinary mortals – even if you don’t plan to use a garbage collector. There are many more and even nastier tricks, such as scattering the bits of a pointer around in different words.

There are legitimate reasons to disguise pointers (e.g., the xor trick in exceptionally memory-constrained applications), but not as many as some programmers think.

A disguised pointer can still be found by a careful garbage collector if its bitpattern is stored in memory with a wrong type (e.g., long or char[4]) and is still properly aligned. Such pointers are called traceable.

The standard library allows a programmer to specify where there are no pointers to be found (e.g., in an image) and what memory should not be reclaimed even if the collector can’t find a pointer into it (§iso.20.6.4):

void declare_reachable(void* p); // the object pointed to by p must not be collected
template<typename T>
T* undeclare_reachable(T* p); // undo a declare_reachable()

void declare_no_pointers(char* p, size_t n); // p[0:n) holds no pointers
void undeclare_no_pointers(char* p, size_t n); // undo a declare_no_pointers()

C++ garbage collectors have traditionally been conservative collectors; that is, they do not move objects around in memory and have to assume that every word in memory might contain a pointer. Conservative garbage collection is more efficient than it is reputed to be, especially when a program doesn’t generate much garbage, but declare_no_pointers() can make it very efficient by safely eliminating large parts of memory from consideration. For example, we might use declare_no_pointers() to tell the collector where our photographic images are in an application, so as to allow the collector to ignore potentially gigabytes of non-pointer data.

A programmer can inquire which rules for pointer safety and reclamation are in force:

enum class pointer_safety {relaxed, preferred, strict };

pointer_safety get_pointer_safety();

The standard says (§iso. “a pointer value that is not a safely-derived pointer value is an invalid pointer value unless the referenced complete object is of dynamic storage duration and has previously been declared reachable ... the effect of using an invalid pointer value (including passing it to a deallocation function) is undefined.”

The enumerators mean:

relaxed: Safely-derived and not safely-derived pointers are treated equivalently (as in C and C++98). Collect every object that does not have a safely derived or traceable pointer to it.

preferred: Like relaxed, but a garbage collector may be running as a leak detector and/or a detector of dereferences of “bad pointers.”

strict: Safely-derived and not safely-derived pointers may be treated differently; that is, a garbage collector may be running and will ignore pointers that’s not safely derived.

There is no standard way of saying which alternative you prefer. Consider that a quality-of-implementation issue or a programming environment issue.

34.6. Uninitialized Memory

Most of the time, it is best to avoid uninitialized memory. Doing so simplifies programming and eliminates many kinds of errors. However, in relatively rare cases, such as when writing memory allocators, implementing containers, and dealing directly with hardware, direct use of uninitialized memory, also known as raw memory, is essential.

In addition to the standard allocator, the <memory> header provides the fill* family of functions for dealing with uninitialized memory (§32.5.6). They share the dangerous and occasionally essential property of using a type name T to refer to space sufficient to hold an object of type Trather than to a properly constructed object of type T. These functions are intended primarily for implementers of containers and algorithms. For example, reserve() and resize() are most easily implemented using these functions (§13.6).

34.6.1. Temporary Buffers

Algorithms often require temporary space to perform acceptably. Often, such temporary space is best allocated in one operation but not initialized until a particular location is actually needed. Consequently, the library provides a pair of functions for allocating and deallocating uninitialized space:

template<typename T>
pair<T*,ptrdiff_t> get_temporary_buffer(ptrdiff_t); //
allocate, don't initialize
template<typename T>
void return_temporary_buffer(T*); //
deallocate, don't destroy

A get_temporary_buffer<X>(n) operation tries to allocate space for n or more objects of type X. If it succeeds in allocating some memory, it returns a pointer to the first uninitialized space and the number of objects of type X that will fit into that space; otherwise, the second value of the pair is zero. The idea is that a system may keep space ready for fast allocation so that requesting space for n objects of a given size may yield space for more than n. It may also yield less, however, so one way of using get_temporary_buffer() is to optimistically ask for a lot and then use what happens to be available.

A buffer obtained by get_temporary_buffer() must be freed for other use by a call of return_temporary_buffer(). Just as get_temporary_buffer() allocates without constructing, return_temporary_buffer() frees without destroying. Because get_temporary_buffer() is low-level and likely to be optimized for managing temporary buffers, it should not be used as an alternative to new or allocator::allocate() for obtaining longer-term storage.

34.6.2. raw_storage_iterator

The standard algorithms that write into a sequence assume that the elements of that sequence have been previously initialized. That is, the algorithms use assignment rather than copy construction for writing. Consequently, we cannot use uninitialized memory as the immediate target of an algorithm. This can be unfortunate because assignment can be significantly more expensive than initialization, and to initialize immediately before overwriting is a waste. The solution is to use a raw_storage_iterator from <memory> that initializes instead of assigns:

template<typename Out, typename T>
class raw_storage_iterator : public iterator<output_iterator_tag,void,void,void,void> {

Out p;
explicit raw_storage_iterator(Out pp) : p{pp} { }
raw_storage_iterator& operator*() { return *this; }

raw_storage_iterator& operator=(const T& val)
new(&*p) T{val}; //
place val in *p (§11.2.4)
return *this;

raw_storage_iterator& operator++() {++p; return *this; } // pre-increment
raw_storage_iterator operator++(int) // post-increment
auto t = *this;
return t;

A raw_storage_iterator should never be used to write to initialized data. This tends to limit its use to the depth of implementations of containers and algorithms. Consider generating a set of permutations (§32.5.5) of strings for use in testing:

void test1()
auto pp = get_temporary_buffer<string>(1000); //
get uninitialized space
if (pp.second<1000) {
// ... handle allocation failure ...
auto p = raw_storage_iterator<string*,string>(pp.first); //
the iterator
[&]{ next_permutation(seed,seed+sizeof(seed)–1); return seed; });

This is a somewhat contrived example because I see nothing wrong in allocating default initialized storage for the strings and then assigning the test strings. Also, it fails to use RAII (§5.2, §13.3).

Note that there are no == or != operators for raw_storage_iterator, so don’t try to use it to write to a [b:e) range. For example iota(b,e,0)40.6) will not work if b and e are raw_storage_iterators. Don’t mess with uninitialized memory unless you absolutely have to.

34.7. Advice

[1] Use array where you need a sequence with a constexpr size; §34.2.1.

[2] Prefer array over built-in arrays; §34.2.1.

[3] Use bitset if you need N bits and N is not necessarily the number of bits in a built-in integer type; §34.2.2.

[4] Avoid vector<bool>; §34.2.3.

[5] When using pair, consider make_pair() for type deduction; §

[6] When using tuple, consider make_tuple() for type deduction; §

[7] Use unique_ptr to represent exclusive ownership; §34.3.1.

[8] Use shared_ptr to represent shared ownership; §34.3.2.

[9] Minimize the use of weak_ptrs; §34.3.3.

[10] Use allocators (only) when the usual new/delete semantics is insufficient for logical or performance reasons; §34.4.

[11] Prefer resource handles with specific semantics to smart pointers; §34.5.

[12] Prefer unique_ptr to shared_ptr; §34.5.

[13] Prefer smart pointers to garbage collection; §34.5.

[14] Have a coherent and complete strategy for management of general resources; §34.5.

[15] Garbage collection can be really useful for dealing with leaks in programs with messy pointer use; §34.5.

[16] Garbage collection is optional; §34.5.

[17] Don’t disguise pointers (even if you don’t use garbage collection); §34.5.

[18] If you use garbage collection, use declare_no_pointers() to let the garbage collector ignore data that cannot contain pointers; §34.5.

[19] Don’t mess with uninitialized memory unless you absolutely have to; §34.6.