C/C++ Arena

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:

#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:

Previous: Member initializer lists Next: Challenge: a Fraction class