Learning objectives
After this chapter you should be able to:
Do unaided:
- Write a class's invariant in one sentence and name the data members it ties together.
- Write constructors that establish an invariant with initializer lists in declaration order, and refuse arguments that cannot satisfy it.
- Trace a member function and say where the invariant is temporarily false and why no caller can see it.
- Mark observers
const, and predict the error when aconstobject calls one that is not. - Write arithmetic, comparison and stream-output operators with conventional meaning, and count the copies
a + b + cmakes.
May delegate:
- Member function bodies, from a declaration, invariant and tests you wrote first.
- Hostile tests against your public interface, from an agent in the Adversary role.
Opening case
A composite case, not a real student or course.
Sam is building PA4 tools for the station's sky camera and needs a gain map: one correction factor per region of the frame, two rows by three columns, read from the camera's calibration file. They ask the agent for "a simple C++ matrix class with element access."
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.
PredictWrite the
Imageinvariant, the members that could break it, and how many Adversary attacks will succeed, with a confidence.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 12 · Predict in Chapter 14 →
WriteBy hand: both constructors and
remove_vertical_seam, with a paper trace like Table 13.2.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 12 · Write in Chapter 14 →
SpecifyListing 13.13 with its invariant comment, tests with a
constprobe and refusal tests; public declarations do not change.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 12 · Specify in Chapter 14 →
DelegateThe Pair implements the rest; the Adversary, with the header only, attacks the public interface.
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 12 · Delegate in Chapter 14 →
AuditHeader diff; tests under ASan and UBSan; one test per successful attack; heap totals for
+against+=; the Audit Report.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 12 · Audit in Chapter 14 →
DefendFive minutes: state the invariant, show where each shape-changing member restores it, explain the overflow attack and its fix.
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 12 · Defend in Chapter 14 →
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.
Public data, or private data behind setters and a non-const reference getter
How to catch it Read the public section before any body; Adversary tests
Section 13.3.1 · Tests
Operators with surprising meaning: an operator+ that changes its left operand, an == that ignores pixels
How to catch it Check signatures; test that a + b leaves a unchanged and that a changed copy is unequal
Section 13.3.2 · Tests
Missing const observers; a constructor that skips the size check; unchecked w * h
How to catch it A const probe; search for const_cast and mutable; refusal tests; huge sizes
Section 13.3.3 · Tests
What the chapter covers
- 13.1 The concept, by hand
- 13.1.1 Access control: the fence
- 13.1.2 Invariants: what must always be true
- 13.1.3 Constructors establish the invariant
- 13.1.4 Members preserve the invariant: a worked example
- 13.1.5
constmember functions - 13.1.6 Operator overloading with conventional meaning
- 13.1.7 Comparison in C++20: defaulted
==and<=> - 13.1.8 Operators on
Image, and what they hide
- 13.2 Predict and check
- 13.3 What agents get wrong here
- 13.3.1 Public data, and public data in disguise
- 13.3.2 Operators with surprising meaning
- 13.3.3 Missing
const, and constructors that skip the check
- 13.4 Working with the agent
- 13.5 Lab: The Image fence
- 13.6 Defend it
Cost thread
Operators hide cost behind syntax. For n frames of p pixels, with operator+ as in Listing 13.8, r = a + b + c + d allocates n − 1 new images of p bytes; r = a; r += b; r += c; r += d; allocates one. Both do the same additions.
Summary
- A class exists to protect an invariant tying its data members together; if the members vary independently, a
structwith public data is the honest choice. - Access control is a compile-time fence; at
-O2an inline accessor compiled to the same instructions as a public member. - Constructors establish the invariant through the initializer list (run in declaration order), and throw rather than create a broken object.
- Members may break the invariant internally but must restore it before returning; build aside, then commit, and offer no setter or non-
constreference that changes one part alone. - Observers are
const; operators keep their conventional meaning (+built on+=, symmetric operators as non-members,<<returning the stream, defaulted==and<=>comparing members in declaration order). - Operator syntax hides cost: Valgrind confirmed three 12 MB copies for
a + b + c + dagainst one for the+=version.
Key terms
- class: a user-defined type with data members and member functions, declared with
class(members private by default) orstruct(public by default). - access specifier: the keyword
publicorprivate, which decides which code may name a member. - encapsulation: keeping a class's data private so that only member functions that preserve its invariant can change it.
- class invariant: a condition on a class's data members that holds after construction and around every public member call.
- constructor: a member function, named after the class, that runs when an object is created and establishes its invariant.
- member initializer list: the list after a constructor's colon that initializes data members directly, in declaration order.
constmember function: a member function that cannot change data members and can be called onconstobjects.this: the hidden pointer, inside a member function, to the object the function was called on.- operator overloading: defining what an operator such as
+means for a user-defined type, by writing a function namedoperator+. - defaulted comparison: an
operator==oroperator<=>declared= default, which compares members in declaration order. - three-way comparison (
<=>): the C++20 operator that reports less, equal or greater in one call.
Connections
Where this chapter and another point to each other most often.
- Chapter 12: Moving to C++: Is It Really C++?
Chapter 12 showed that the memory model under C++ is the C memory model; a class does not change where bytes live. - Chapter 14: Resource Ownership: RAII, the Rules of Zero, Three, and Five, and Smart Pointers
Resource management is Chapter 14's subject; leaving it out isolates the invariant. ### 13.1.1 Access control: the fence A class in C++ is a user-defined type with data members and member functions.
Further reading
- Stroustrup and Sutter (2024), C++ Core Guidelines, sections C, Con and the operator rules C.160 to C.170: numbered rules with reasons, in the vocabulary reviewers use.
- Meyer (1992): the original case that invariants, preconditions and postconditions specify a class.
- Parnas (1972): why hiding a design decision behind an interface is the point of a module.
- Stroustrup (2024): classes and operators at a beginner's pace, with other examples.