Why std::chrono::steady_clock Is the Unsung Hero of Reliable Timing in C++
Share this article
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.