Module 4 — Summary¶
The essentials¶
- Private is not encapsulation. Encapsulation is making invalid states impossible to reach: the data is private, every public operation preserves the invariant, and the interface expresses what the system does. Two of the three are usually missing.
- A setter is a public representation with a function call in front of it. It exposes the field instead of the capability, so whoever calls it must know the rule — and nothing makes them remember.
- An invariant is established at construction and preserved by every public operation. It is a sentence that must be true of every valid object at every observable moment. A rule that is documented but breakable is a wish.
- Design in this order: invariant → interface → representation. Writing the operations before the fields is what stops you producing a getter and a setter per member.
- Public members should express what the system does:
canBorrowMore(),lend(),applyRaise()— notgetCount(),setOnLoan(),setSalary(). A getter is right for a read-only fact with no rule attached; a setter rarely is. - A refused operation must change nothing. That is what makes an invariant reliable: a caller can attempt something, be told no, and know the object is exactly as it was.
- The representation leaks through four things: a returned mutable reference, a returned
iterator or pointer, a signature naming a container or capacity, and a
const&return whose lifetime the caller can outlive. Only the mutable-reference leak is usually caught by the compiler, and only sometimes. const-correctness is what lets other code hold your object without copying and without the right to modify it. Every observing member function must beconst, or aconst¶meter cannot use it.mutableis correct only for state the caller cannot observe — a cache, a memoised result, a lock. The test is one sentence: can a caller tell that it changed?friendis ordinary for formatters and comparisons and a coupling decision everywhere else: the friend is now part of your implementation and must be re-checked whenever the representation changes.- The test that decides whether it worked: if I changed the private representation completely, would any correct caller need to change? Any observable difference — a signature or a behaviour — is part of the interface.
- Cohesion and coupling: a class should do one job, and know as little as possible about other classes. The sentence "this class knows too much about that one" is a design review on its own.
- The best error message is the one you made impossible to provoke. Taking a
Bookrather than anintdeletes a whole class of failures before they can be written.
The one-sentence version¶
A well-designed type cannot be put into a state its author did not intend, and the way to achieve that is to expose operations rather than data.
Terms you must now be able to define¶
Encapsulation · abstraction · information hiding · invariant · interface versus
implementation · capability versus accessor · const-correctness · overloading on const ·
mutable · friend · cohesion · coupling · representation leak · refusal semantics
See the glossary. Two matter more than the rest for what follows: invariant (Module 6 requires derived classes to preserve their base's) and interface versus implementation (Module 15's dependency inversion is exactly this distinction, applied between modules).
The habit to keep¶
Attack your own class before shipping it. Write the offending line; if it compiles, the invariant is not enforced. Five minutes, and it finds the bugs no compiler can see.
Before you move on¶
- [ ] All warm-up and core exercises attempted, with results checked
- [ ] Lab 4 builds with no warnings, and its attack file is kept
- [ ]
justification.mdhas one row per public member of every type, with a promise and a reason — not a description - [ ] The deleted-members section is written, and it is not empty
- [ ] At least one member is flagged as arguable, with both sides argued
- [ ] A silent diff: your representation can be swapped for another without a caller changing
- [ ] At least one challenge problem attempted, with its rubric applied honestly
- [ ] Self-assessment ≥ 7, and question 3's four verdicts are all correct
- [ ] The skills checklist on the module page ticked with evidence
Further reading¶
- Effective C++ (3rd ed.), Items 18–22 — "make interfaces easy to use correctly and hard to use incorrectly". This module's §10 is a summary of these five items, written by the person who coined the phrasing.
- C++ Primer (5th ed.), chapter 7.1 (access control) and 7.6 (static members, as a contrast: state that belongs to the class, which encapsulation has to account for too).
- A Tour of C++ (3rd ed.), chapter 2 and the "concrete types" section — the argument that a well-designed type is what makes large programs possible at all.
- The C++ Core Guidelines —
C.2(useclassif there is an invariant),C.9(minimise the exposure of members),C.4/C.5(namespace-scope and member functions versus free functions),I.1–I.3(make interfaces explicit and hard to misuse). About ten short rules, and this module is roughly their content. - Working Effectively with Legacy Code (Feathers) — chapter 20, "this class is too big and I don't want it to get bigger". Not about C++, and the best available treatment of extracting an interface from code that already leaked everywhere.
See Further Reading for editions and why these are the right ones.
Next: Module 5 — Operators and Static Members.
Module 5 gives your types the operators the rest of the language expects — << for printing,
== for comparison, < for sorting — which is where const-correctness and the
friend/accessor decision from this module become concrete requirements rather than good
practice.