Step 8 of 9
Keeping invariants
An invariant is a rule about an object's data that is true at all times when viewed from outside: a health value is between 0 and its maximum; a date's month is between 1 and 12; a sorted list is sorted.
Invariants are the real reason for private. If only member functions can touch the data, only they can break the rule, so you can check and enforce it in one place:
- Every constructor must establish the invariant (clean up bad inputs, or refuse them).
- Every member function must preserve it (never leave the object in a broken state).
#include <algorithm>
#include <iostream>
class Volume {
public:
explicit Volume(int level) : level_(std::clamp(level, 0, 10)) {}
void up(int n) {
if (n > 0) level_ = std::min(10, level_ + n);
}
void down(int n) {
if (n > 0) level_ = std::max(0, level_ - n);
}
int level() const { return level_; }
private:
int level_;
};
int main() {
Volume v(25);
std::cout << v.level() << " ";
v.down(3);
v.down(-100);
v.up(1);
std::cout << v.level() << "\n";
}
10 8
Tools
std::min(a, b), std::max(a, b) and std::clamp(x, lo, hi) from <algorithm> make boundary rules short and readable. std::clamp(25, 0, 10) is 10.
Handle bad input deliberately
A negative "down" amount would secretly turn into an increase. The class decides what to do with nonsense (here: ignore it) instead of letting it corrupt the state. Whatever the policy, every public function must leave the object valid, so users of the class never need to double-check it. When you design a class, write the invariant down in a comment first; the code follows from it.
Your turn: write class Health:
Health(int max): max below 1 becomes 1; starts fullvoid damage(int n)andvoid heal(int n): negative amounts are ignored; hp stays within0..maxint hp() const,int max() const,bool alive() const
Previous: Member initializer lists Next: Challenge: a Fraction class