Encapsulation and Abstraction
The two pillars that make object-oriented design powerful — hiding details and protecting truths about your data.
A class is a contract. From the outside, it offers a set of operations and promises certain things will always be true. From the inside, it is whatever code is needed to keep those promises.
Two ideas make this work:
- Encapsulation — bundling data with the code that controls it and hiding the data from the outside world.
- Abstraction — exposing a simple interface that captures the what without committing to the how.
These aren't C++ features; they are design principles that the language gives you tools to apply.
Invariants: truths your class must keep
An invariant is a rule about a class's state that must always hold while no method is in progress. Some examples:
| Class | Invariant |
|---|---|
BankAccount | balance >= 0 |
Date | 1 <= month <= 12 |
Stack<T> | size <= capacity and size >= 0 |
SortedVector<int> | the elements are non-decreasing |
If a field is public, anyone can break the invariant by assigning
to it. Making it private and offering controlled methods is how
you defend the invariant.
A Date that cannot be invalid
There is no way a Date object can exist with month == 99.
Outside code never sees the fields, only the safe getters. The
class is encapsulated.
Abstraction: same interface, different implementations
Imagine a TextStorage that you can append to and read by index.
You could implement it as a std::string, a std::vector<char>, a
rope data structure, even an on-disk file. The interface — the
public method names and meanings — stays the same. Code that uses a
TextStorage does not care which implementation it got.
That is abstraction. You name the what (append, get) and hide
the how (the data structure). Later you can swap implementations
without changing any caller.
Why this matters
When a program is small you can keep everything in your head. When it grows past a few hundred lines, you need ways to reason about pieces without re-reading the whole codebase. Encapsulation gives you those pieces:
- You can change a class's internals without breaking its users, as long as you keep its public interface stable.
- You can test a class in isolation.
- You can let multiple people work on different classes without stepping on each other.
This is the difference between programs that grow gracefully and programs that calcify under their own weight.
Getters, setters, and a word of caution
It's tempting to write a getter and a setter for every field. That turns your class right back into a struct with extra steps:
// anti-pattern
class Point {
public:
int x() const { return x_; }
void set_x(int v) { x_ = v; }
int y() const { return y_; }
void set_y(int v) { y_ = v; }
private:
int x_, y_;
};If a class has no invariants and no behaviour, just use a struct.
Reach for class when you have something to protect.
Test your understanding
What is an invariant of a class?
A method that never changes.
A field whose value is fixed.
A property of the object's state that must always hold whenever no method is in progress.
A type alias used by the class.
What is the main reason to make fields private?
The compiler runs faster.
It hides the field from the debugger.
It prevents outside code from putting the object in an invalid state, letting the class defend its invariants.
It is required for inheritance.
Next: how objects come to life and how they clean up after themselves — constructors and destructors.