Learning objectives
After this chapter you should be able to:
Do unaided:
- Draw a program's lifetime timeline with every constructor and destructor call, and explain RAII with it.
- Explain shallow versus deep copy, predict what a shallow copy does to memory, and write the five special members for an owning class.
- Explain what
std::movedoes (a cast) and state the moved-from state of a class you wrote. - Choose
unique_ptr,shared_ptr,weak_ptror a raw observer for an ownership situation, with a one-sentence reason. - Predict how copy and move cost grow with size, and check with a timing run.
May delegate:
- A resource-owning class implemented from your declaration and ownership rules, followed by your own rule-of-five audit.
Opening case
A composite case, not a real student or course.
Priyanka's PA4 box blur works on the station's sky-camera frames. She wants a scratch area for intermediate rows, so she asks the agent for "a small Buffer class that owns a heap array of doubles, fast, no vector overhead." The answer arrives in seconds:
The chapter continues from here.
The loop in this chapter
Open a step to see what it asks of you and how it plays out in the Pair phase.
PredictWithout the agent, draw the timeline and memory after
Buffer b = a;, predict the ASan error kind, and write cost formulas for copy and move.Step 1 of 6 · You, no AI
What you hand in: A written prediction: the output, the memory state, the operation count, and how sure you are.
Why this step: A prediction written before the run is the only way to learn whether your model of the machine is right. A miss is the most useful result there is, because it shows exactly where the model is wrong.
The first prediction is never delegated.
Ask yourself: What will this print, what will memory look like when it ends, and how many operations will it take?
Log the prediction in your work log · Predict in every chapter · ← Predict in Chapter 13 · Predict in Chapter 15 →
WriteBy hand, the copy constructor and copy-and-swap assignment.
Step 2 of 6 · You, no AI
What you hand in: One small piece written by hand, to build the mental model.
Why this step: You can only check what you could have built. Writing a small piece yourself gives you the model you will need to judge the agent's version later.
Ask yourself: Could I write this piece again tomorrow without notes?
Browse the companion code · Write in every chapter · ← Write in Chapter 13 · Write in Chapter 15 →
SpecifyThe declaration, the ownership rules (never delegated), tests from the rules, and a budget: copy proportional to n, move constant.
Step 3 of 6 · You
What you hand in: The header or class interface, unit tests, a complexity budget and the ownership rules.
Why this step: The specification is the contract the code will be held to. Tests written first, a cost budget and ownership rules turn "looks right" into something you can check.
The ownership and lifetime section of a specification is never delegated.
Ask yourself: Who allocates, who frees, what does it cost, and what input would break it?
Prompt and specification templates · Specify in every chapter · ← Specify in Chapter 13 · Specify in Chapter 15 →
DelegateThe Pair writes the remaining members or a rule-of-zero rewrite; save the transcript.
Step 4 of 6 · The agent
What you hand in: The implementation from your specification, or an explanation in the Tutor phase, with the prompts and transcript saved.
Why this step: The agent does the typing; you keep the judgment. Saving the prompts and the transcript makes the delegation something you can audit afterward.
Pair Pair phase: the agent writes small units from your specification, and you audit every line.
Ask yourself: Did I hand over my specification and tests, and did I save what the agent said?
Prompt templates · Delegate in every chapter · ← Delegate in Chapter 13 · Delegate in Chapter 15 →
AuditThe five-member checklist, then ASan, Valgrind, the growth count and the timing; record each defect with its instrument.
Step 5 of 6 · You and the tools
What you hand in: The Audit Report: tests, sanitizer and Valgrind output, your own cost derivation against a timing plot, the defects found, and a keep-or-rewrite decision.
Why this step: What the agent says is testimony until an instrument confirms it. Tests, sanitizers, Valgrind and a timing plot are evidence.
The final judgment that the code is correct is never delegated.
Ask yourself: What did the sanitizer report, does the timing plot match my derivation, and would I keep this code or rewrite it?
The Audit Report template · Audit in every chapter · ← Audit in Chapter 13 · Audit in Chapter 15 →
DefendWhere the array is freed, what a move leaves, why each pointer type.
Step 6 of 6 · You, live
What you hand in: A five-minute oral defense.
Why this step: If you can explain it live, you own it. The oral checks that the understanding is yours and not the agent's.
Ask yourself: Why this bound, what input breaks it, and where is this memory freed?
How the loop works · Defend in every chapter · ← Defend in Chapter 13 · Defend in Chapter 15 →
What agents get wrong here
Patterns observed in agent-written code, to test for, not guarantees. If the agent no longer makes the mistake, your job is to prove it got it right, with the same instrument.
The rule of three without moves, or a move constructor without noexcept
How to catch it Count copies and moves over 1,000 insertions; static_assert(std::is_nothrow_move_constructible_v<T>)
Section 14.3.1
Raw delete on memory a unique_ptr owns
How to catch it ASan attempting double-free; every delete and free needs a written reason
Section 14.3.2 · AddressSanitizer
shared_ptr everywhere, "the safest choice"
How to catch it Name two owners with non-nested lifetimes for each; LeakSanitizer on a build-and-destroy test
Section 14.3.3 · LeakSanitizer, Tests, Timing and log-log plots, Your own trace
What the chapter covers
- 14.1 The concept, by hand
- 14.1.1 Destructors and the object lifetime timeline
- 14.1.2 RAII for files, buffers and locks
- 14.1.3 The double free: what the default copy does
- 14.1.4 The rules of three and five, and copy-and-swap
- 14.1.5 Move semantics and what
std::moveactually does - 14.1.6 The moved-from state
- 14.1.7 The rule of zero
- 14.1.8
unique_ptr,shared_ptr,weak_ptrand raw observers - 14.1.9 A
shared_ptrcycle, andweak_ptrto break it
- 14.2 Predict and check
- 14.2.1 Construction and destruction order
- 14.2.2 Copy cost against move cost
- 14.3 What agents get wrong here
- 14.3.1 The rule of three without move
- 14.3.2 Raw
deletemixed withunique_ptr - 14.3.3
shared_ptreverywhere
- 14.4 Working with the agent
- 14.4.1 The spec you write first
- 14.4.2 Prompt patterns
- 14.4.3 What to check, in order
- 14.5 Lab: A rule-of-five audit of an agent-written class
- 14.6 Defend it
Cost thread
Table 14.3 gives the median of five runs of copy_vs_move.cpp on the course container (g++ 13.3, -O2, a 2.1 GHz server processor), with the range of copy times.
Summary
- Destructors run at points you can mark on the lifetime timeline, so RAII releases files, buffers and locks on every exit path.
- The generated copy is memberwise, so a raw owning pointer is copied shallowly: one 32-byte block freed twice from one closing brace.
- Write any of the five special members and you must consider all five; copy-and-swap is self-assignment safe. The rule of zero is the default, but only as good as its members.
std::moveis a cast. Withoutnoexcepton the move constructor,std::vectorgrowth made 1,023 copies and no moves.unique_ptris no bigger than a raw pointer, even with aFILE*deleter.shared_ptrpays on every copy (30 to 60 times a raw observer once a thread had run, on our machine), and a two-way link leaked 152 bytes thatweak_ptrrecovered.
Key terms
- destructor: the member
~T(), run when an object's lifetime ends, that releases what it owns. - RAII: acquiring a resource in a constructor and releasing it in the destructor.
- shallow copy: a copy of a pointer but not of the resource behind it.
- deep copy: a copy of the resource itself, so each object owns its own.
- copy constructor:
T(const T&), which builds a new object as a copy. - copy assignment operator:
T& operator=(const T&), which overwrites an existing object with a copy. - copy-and-swap: assignment by copying into a temporary, then swapping it with
*this. - rule of three: a class needing a destructor, copy constructor or copy assignment needs all three.
- rule of five: the rule of three plus the two move members.
- rule of zero: members manage every resource, so the class declares none of the five.
- move semantics: taking a soon-discarded object's resources instead of copying them.
- rvalue reference:
T&&, which binds to temporaries and tostd::moveresults. std::move: a cast to an rvalue reference; it moves nothing.- moved-from state: what a move leaves behind; it must stay destroyable and assignable.
noexcept: a promise not to throw.std::unique_ptr: a move-only smart pointer that is an object's sole owner.std::shared_ptr: a smart pointer sharing ownership through a reference count in a control block.std::weak_ptr: a non-owning observer of ashared_ptr-managed object.- observer pointer: a raw pointer or reference to an object owned elsewhere; it never deletes.
- custom deleter: a callable a smart pointer runs instead of
delete. - smart pointer: a class that holds a pointer and enforces an ownership rule in its special members.
- reference count: the number of
shared_ptrs that currently own an object, kept in its control block.
Companion code
10 files for this chapter, compiled with the book's flags.
Read the code Download ch14.zip
Connections
Where this chapter and another point to each other most often.
- Chapter 6: The Stack and the Heap: Dynamic Memory and Ownership
It has a constructor, a destructor and an index operator, and everynew[]has its matchingdelete[], the rule Chapter 6 drilled into her. - Chapter 8: Growable Arrays and Linked Lists: The Cost of Growth
growis a function template, which Chapter 16 explains; readgrow<ThreeOnly>as "run this code withTreplaced byThreeOnly." 1,023 is 1 + 2 + 4 + ... + 512, the relocations as this library's vector doubled its capacity to 1,024 (Chapter 8). - Chapter 12: Moving to C++: Is It Really C++?
It is common in code ported from C (Chapter 12). - Chapter 13: Classes, Invariants, and Operator Overloading
In Listing 14.8 it is a struct whoseoperator()(Chapter 13) lets its objects be called like a function. - Chapter 15: Inheritance, Polymorphism, and Composition: Reviewing an Agent's Design
In PA5,stn::Stationowns sensors throughstd::vector<std::unique_ptr<Sensor>>(Chapter 15): Listing 14.9. Owners and a raw observer (fragment ofobserver.cpp). - Chapter 17: Binary Search Trees
From Chapter 17: Each node owns its two children throughstd::unique_ptr(Chapter 14), so every node has exactly one owner: its parent'sleftorrightlink, or, for the root, the variableroot. - Chapter 18: Hash Tables, Exceptions, and the Search Study
Listing 14.2 wraps a CFILE*: the constructor opens the file or throws (Chapter 18; readthrowas "refuse to create the object"), and the destructor closes it. - Chapter 20: A First Look at Concurrency
With one empty thread started and joined at the top ofmain(shared_cost_mt.cpp), it rose to between 20 and 52 ns over eight runs, while the observer stayed near 1 ns: once a program has started a thread, this library updates the count with atomic instructions (Chapter 20), and keeps doing so after the thread ends.
Further reading
- Stroustrup (2013), on resource management and on copy and move: the designer's own account of RAII.
- Meyers (2014), the items on
std::move,noexceptand smart pointers: whystd::movedoes not move. - Stroustrup and Sutter (2024), Core Guidelines C.20 to C.22 and section R: numbered rules to cite in an Audit Report.
- Serebryany et al. (2012): how ASan finds double frees, and so what it cannot see.