The Unsung Hero of Reliable Timing in C++

In the C++ standard library, the chrono facilities provide two primary clocks for measuring time: std::chrono::system_clock and std::chrono::steady_clock. While they both return a time_point and can be used to compute durations, their underlying behavior diverges in a way that has profound implications for developers.

Monotonicity: The Core Difference

steady_clock is monotonic: its time never moves backwards. Every call to steady_clock::now() yields a value that is greater than or equal to any previous call. This guarantees that elapsed‑time calculations are immune to external adjustments such as NTP corrections or manual changes to the system clock.

In contrast, system_clock reflects the real‑world wall clock. If the operating system synchronizes time, system_clock::now() may jump forward or backward, which can corrupt any duration calculation that depends on it.

auto start = std::chrono::steady_clock::now();
// … run something …
auto end = std::chrono::steady_clock::now();

std::chrono::duration<double> elapsed = end - start;
std::cout << "Elapsed time: " << elapsed.count() << " seconds
";

The above snippet is a textbook example of why steady_clock is the natural choice for measuring how long a function takes, how long a loop runs, or how long a timeout should last.

When to Use Which Clock

Use case Clock Why
Measuring elapsed time steady_clock Guarantees monotonicity, immune to system adjustments
Logging a human‑readable timestamp system_clock Provides real‑world time for logs and user interfaces
Scheduling a delayed operation steady_clock Predictable when the system clock changes

Standard library facilities that rely on predictable timing, such as std::jthread::wait_for, explicitly use steady_clock for this reason.

Testing with a Mock Clock

The monotonic nature of steady_clock also makes it ideal for deterministic tests. By abstracting the clock behind an interface, tests can advance time manually without real sleeping, dramatically speeding up test suites.

struct Clock {
    using time_point = std::chrono::steady_clock::time_point;
    virtual time_point now() const = 0;
    virtual ~Clock() = default;
};

struct RealClock : Clock {
    time_point now() const override { return std::chrono::steady_clock::now(); }
};

struct FakeClock : Clock {
    time_point current{};
    time_point now() const override { return current; }
    void advance(std::chrono::milliseconds delta) { current += delta; }
};

// In production
Clock* clock = new RealClock();

// In tests
FakeClock fake;
clock = &fake;
auto start = clock->now();
fake.advance(std::chrono::seconds(1));
auto end = clock->now();
assert(end - start == std::chrono::seconds(1));

This pattern is especially useful for retry logic, event scheduling, or any simulation where reproducibility is paramount.

Takeaway

steady_clock is not about telling you the time of day; it is about reliably telling you how much time has passed. Its monotonic guarantee makes it the go‑to tool for benchmarking, timeouts, and deterministic testing. By wrapping or mocking it, developers can write faster, more reliable tests that are completely independent of the host system’s clock.

For a deeper dive into high‑resolution timing, stay tuned for the next installment on high_resolution_clock.

Source: Sandor Dargo – Clocks Part 3: steady_clock