C++ / CHAPTER 05

Value classes and constructor invariants

Intermediate

Design objects whose valid state survives every operation.

By the end of this chapter

  • Initialize all members
  • Validate before modifying state
  • Use const member functions for observation

The idea

A class can hide representation and expose operations that preserve a rule. A constructor should establish that rule immediately; a member initializer list initializes fields before the constructor body runs. A const member function can observe ordinary members without modifying them through this. These constraints make a small value type easier to use correctly.

When an operation may fail, check the request before changing the object. Copying a class whose members are ordinary value types creates an independent value, but a class containing pointers requires an explicit ownership design. Prefer standard owning members and their established copy behavior instead of manually managing allocations. State the range of arithmetic inputs so preserving a business invariant does not conceal integer overflow.

C++ example
#include <iostream>
#include <stdexcept>
class Seats {
  int left;
public:
  explicit Seats(int n) : left(n) {
    if (n < 0) throw std::invalid_argument("capacity");
  }
  bool reserve(int n) {
    if (n < 0 || n > left) return false;
    left -= n; return true;
  }
  int remaining() const { return left; }
};
int main() { Seats s(5); s.reserve(7); std::cout << s.remaining(); }
Result
5

The excessive request returns before mutation.

Try it yourself

Implement a bounded counter supporting only increments that fit its remaining capacity.

Use your own editor for this exercise. Runnable challenges on CodeQuest are available for JavaScript, Python, and SQL.

CHECK YOUR UNDERSTANDING

Chapter checkpoint

Which method declaration communicates an observation that does not modify ordinary instance fields?