Preface xv
Part 1: Memory in C++
1
Objects, Pointers, and References 3
Technical requirements 3
Representation of memory in C++ 4
Objects, pointers, and references 4
Understanding the fundamental
properties of objects 10
Object lifetime 10
Object size, alignment, and padding 12
Copy and movement 18
Arrays 29
Summary 30
2
Things to Be Careful With 31
Different kinds of evil 32
Ill-formed, no diagnostic required 32
Undefined behavior 32
Implementation-defined behavior 33
Unspecified behavior (not documented) 34
The ODR 34
Erroneous behavior 35
Pointers 35
Uses of pointer arithmetic within an array 35
Pointer interconvertibility 36
Uses of pointer arithmetic within an object 37
Type punning 39
Type punning through members of a union 39
The intptr_t and uintptr_t types 41
The std::memcpy() function 42
The special cases of char*, unsigned char*,
and std::byte* 43
The std::start_lifetime_as<T>() function 43
Summary 44
3
Casts and cv-qualifications 45
Technical requirements 45
What is a cast? 46
Safety in the type system – cvqualifications
47
The C++ casts 48
Your best friend (most of the time) – static_cast 49
A sign something’s wrong – dynamic_cast 50
Playing tricks with safety – const_cast 52
“Believe me, compiler” – reinterpret_cast 53
I know the bits are right – bit_cast 54
Somewhat unrelated, but still – duration_cast 54
The reviled one – the C cast 55
Summary 56
Part 2: Implicit Memory Management Techniques
4
Using Destructors 59
Technical requirements 60
On destructors: a short recap 60
Managing resources 62
Exception handling… or not? 64
The RAII idiom 65
RAII and C++’s special member functions 67
Some pitfalls 67
Destructors should not throw 67
Know thy destruction order 69
Standard resource management
automation tools 73
unique_ptr<T> and shared_ptr<T> 74
lock_guard and scoped_lock 75
stream objects 77
vector<T> and other containers 78
Summary 78
5
Using Standard Smart Pointers 79
Technical requirements 80
The standard smart pointers 80
On the exposition of intent through
function signatures 82
Type unique_ptr 84
Handling objects 86
Handling arrays 88
Custom deleters 89
make_unique 93
Types shared_ptr and weak_ptr 96
Usefulness and costs 98
make_shared() 98
What about weak_ptr? 99
When to use raw pointers 103
Summary 105
6
Writing Smart Pointers 107
Technical requirements 108
Ownership semantics 108
Writing your own (naïve) unique_ptr 109
Type signature 109
Special member functions 112
Pointer-like functions 114
Writing your own (naïve) shared_ptr 116
A few words on make_shared() 123
Writing a policy-based
duplicating pointer 124
Detection through interfaces 126
Detection through traits 127
Detection through concepts 129
Some not-so-smart yet useful
smart pointers 130
A non_null_ptr type 130
An observer_ptr type 132
Summary 133
Part 3: Taking Control (of Memory
Management Mechanisms)
7
Overloading Memory Allocation Operators 137
Why would one overload
allocation functions? 138
Brief overview of the C language
allocation functions 138
Overview of the C++ allocation
operators 140
Global allocation operators 141
Non-throwing versions of the
allocation operators 145
The most important operator new:
placement new 148
Member versions of the allocation operators 151
Alignment-aware versions of the
allocation operators 153
Destroying delete 154
Summary 157
8
Writing a Naïve Leak Detector 159
Technical requirements 160
The plan 160
A first implementation
(that almost works) 164
The Accountant singleton class 164
Implementing the new and new[] operators 168
Implementing the delete and delete[] operators 169
Visualizing it all 170
Identifying (and fixing) the problems 176
Revisiting our implementation
(and lessons learned) 179
Summary 181
9
Atypical Allocation Mechanisms 183
Technical requirements 184
Placement new and memory-mapped
hardware 184
Simplifying nothrow new usage 187
Out-of-memory situations and
new_handler 192
Standard C++ and exotic memory 194
A fictional shared memory API 196
A handmade user code example 199
A standard-looking user code equivalent 201
Summary 205
10
Arena-Based Memory Management and Other Optimizations 207
Technical requirements 208
Arena-based memory management 208
Specific example – size-based implementation 209
Generalizing to SizeBasedArena<T,N> 219
When parameters change 221
Chunked pools 222
Summary 233
11
Deferred Reclamation 235
Technical requirements 236
What do we mean by deferred
reclamation? 236
Reclamation (without finalization) at
the end of the program 239
Reclamation and finalization at the
end of the program 243
Reclamation and finalization at the
end of the scope 247
Summary 255
Part 4: Writing Generic Containers (and a Bit More)
12
Writing Generic Containers with Explicit Memory Management 259
Technical requirements 261
Writing your own vector<T>
alternative 261
Representational choices for a container of
contiguous elements 262
The implementation of Vector<T> 263
Writing your own forward_list<T>
alternative 274
Representational choices for a
node-based container 275
The implementation of ForwardList<T> 275
Better memory management 282
A more efficient Vector<T> 284
Using low-level standard facilities 285
Const or reference members and std::launder() 294
Summary 297
13
Writing Generic Containers with Implicit Memory Management 299
Technical requirements 300
Why explicit memory management
complicates our implementation 300
Implicit memory management with a
smart pointer 302
Impact on the naïve Vector<T> implementation 302
Impact on the sophisticated Vector<T>
implementation 306
Consequences of this redesign 311
Generalizing to ForwardList<T>? 312
Attempt - making each node responsible for
its successor 312
Attempt: making the head pointer responsible
for the other nodes 317
Summary 319
14
Writing Generic Containers with Allocator Support 321
Technical requirements 322 Why allocators? 322
Traditional allocators 323
Before C++11 323
Traditional allocators with
contemporary standards 348
Managing traditional allocator lifetime 351
Irritants with traditional allocators 357
Polymorphic memory
resource allocators 358
Nested allocators 360
Allocators and data collection 361
Upsides and costs 363
Summary 364
15
Contemporary Issues 365
Technical requirements 366
Starting object lifetime
without constructors 366
Trivial relocation 370
Type-aware allocation and
deallocation functions 374
Summary 377
Annexure
Things You Should Know 379
struct and class 379
std::size_t 381
The sizeof operator 381
Assertions 381
Undefined behavior 382
Type traits 383
The std::true_type and std::false_type
traits 384
The std::conditional<B,T,F> trait 385
Algorithms 387
Functors (function objects)
and lambdas 388
Friends 389
The decltype operator 392
Perfect forwarding 393
The singleton design pattern 396
Instantiation at program startup 397
Instantiation of the first call 398
The std::exchange() function 399
Index 401
Other Books You May Enjoy 410
Harness the power of C++ to build smaller, faster, safer, and more predictable programs using the latest techniques and best practices to optimize performance and ensure reliability in your software development projects
C++ programmers often face challenges in allocating and managing memory efficiently, especially given the diverse needs of real-time systems, embedded systems, games, and conventional desktop applications. This book offers a targeted approach to address the unique memory constraints of each domain.
Written by an ISO C++ Standards Committee member, Patrice Roy, this guide covers fundamental concepts of object lifetime and memory organization to help you write simpler and safer programs. You’ll learn how to control memory allocation mechanisms, create custom containers and allocators, and adapt allocation operators to suit your specific requirements, making your programs smaller, faster, safer, and more predictable.
Starting with core principles of memory management, this book introduces modern facilities that simplify your work and then dives into memory management mechanics, building solutions for specific application needs, and measuring their impact on your program’s behavior.
By the end of this book, you’ll be able to write secure programs that handle memory optimally for your application domain. You will also have a strong grasp of both high-level abstractions for safer programs and low-level abstractions that allow detailed customization.
This book is for programmers who find C++ memory management challenging yet want to master it. It’s ideal for those seeking greater control over memory allocation to make their programs smaller, faster, and safer. Whether you're a seasoned C++ developer or transitioning from other languages, you'll gain insights into how C++ can enhance your programming. It’s especially valuable if you’re working in constrained environments, like embedded systems or game development. Prior experience with programming concepts, such as generic and concurrent programming, is helpful.