библиотека · утилитыlibrary · utilities

std::chrono

Типобезопасное представление времени — duration и time_point вместо голых чисел (миллисекунд, тиков) в int или long. Единицы измерения проверяются компилятором, а не подразумеваются комментарием.A type-safe representation of time — duration and time_point instead of raw numbers (milliseconds, ticks) stuffed into an int or long. Units are checked by the compiler rather than implied by a comment.

часто используетсяcommonly used типобезопасные единицы времениtype-safe time units

Что этоWhat it is

std::chrono — библиотека из <chrono>, вводящая три ключевые сущности: duration — промежуток времени с явной единицей измерения на уровне типа (секунды, миллисекунды, произвольное отношение), time_point — момент времени относительно конкретных часов, clock (steady_clock, system_clock, high_resolution_clock) — источник текущего времени с определённой семантикой (монотонность или привязка к календарю).std::chrono is a library from <chrono> introducing three key entities: duration — a span of time with an explicit unit at the type level (seconds, milliseconds, an arbitrary ratio), time_point — a moment in time relative to a specific clock, and clock (steady_clock, system_clock, high_resolution_clock) — a source of the current time with defined semantics (monotonicity or calendar alignment).

до C++11 — голые числаbefore C++11 — raw numbers
void sleep_ms(long ms);   // единица — только в имени// the unit lives only in the name
                            // или комментарии, компилятор не проверяет// or a comment — the compiler doesn't check it
sleep_ms(1000);            // секунда? миллисекунда?// a second? a millisecond?
                            // легко перепутать порядок величины// easy to be off by an order of magnitude
std::chrono — типобезопасноstd::chrono — type-safe
void sleep_for(std::chrono::milliseconds ms);
sleep_for(std::chrono::seconds(1));  // компилятор сам// the compiler converts
                                       // приведёт единицы// the units itself

Зачем ввелиWhy it was introduced

Время в виде голых чисел было источником систематических, трудноуловимых ошибок:Time represented as raw numbers was a source of systematic, hard-to-catch bugs:

01

Единица измерения — только соглашение, не проверяется компиляторомThe unit was only a convention, not checked by the compiler

Функция, принимающая long «миллисекунды», ничем не отличается на уровне типа от функции, принимающей «секунды» — перепутать единицу при вызове (передать секунды туда, где ожидаются миллисекунды) компилятор не мог обнаружить.A function taking a long "milliseconds" is indistinguishable at the type level from one taking "seconds" — mixing up the unit at the call site (passing seconds where milliseconds were expected) was something the compiler couldn't catch.

02

Разные платформенные и библиотечные API использовали разные единицыDifferent platform and library APIs used different units

POSIX-время в секундах, Windows FILETIME в 100-наносекундных интервалах, разные библиотеки — в миллисекундах или микросекундах — конвертация между ними вручную была источником арифметических ошибок и переполнений.POSIX time in seconds, Windows FILETIME in 100-nanosecond intervals, various libraries in milliseconds or microseconds — converting between them by hand was a source of arithmetic bugs and overflow.

03

Нужны часы с гарантированной монотонностью для измерения интерваловInterval measurement needed a clock with guaranteed monotonicity

Системное время (system_clock) может скачком измениться (перевод часов, синхронизация NTP) — для измерения длительности выполнения кода нужны часы, которые никогда не идут назад; steady_clock даёт именно такую гарантию на уровне типа.System time (system_clock) can jump (a clock adjustment, NTP sync) — measuring code execution duration needs a clock that never runs backward; steady_clock gives exactly that guarantee at the type level.

Как это работает под капотомHow it works under the hood

std::chrono::duration<Rep, Period> хранит количество тиков типа Rep (например, int64_t) с периодом Period — отношением из std::ratio (например, std::ratio<1, 1000> для миллисекунд). Конвертация между единицами (секунды в миллисекунды и обратно) выполняется компилятором на этапе компиляции через сравнение Period-ов, без ручной арифметики программиста — и без потери типобезопасности при неявном переходе от более грубой единицы к более точной.std::chrono::duration<Rep, Period> stores a tick count of type Rep (e.g. int64_t) with a period Period — a ratio from std::ratio (e.g. std::ratio<1, 1000> for milliseconds). Conversion between units (seconds to milliseconds and back) is performed by the compiler at compile time by comparing Periods, with no manual arithmetic from the programmer — and without losing type safety on an implicit conversion from a coarser unit to a finer one.

#include <chrono>
#include <thread>

using namespace std::chrono;

auto start = steady_clock::now();          // time_point, монотонные часы// time_point, a monotonic clock
std::this_thread::sleep_for(milliseconds(50));
auto end = steady_clock::now();

duration<double> elapsed = end - start;    // duration с Rep=double, Period=1 (секунды)// duration with Rep=double, Period=1 (seconds)
std::cout << elapsed.count() << " s\n";

milliseconds ms(1500);
seconds s = duration_cast<seconds>(ms);    // явное приведение при потере точности// explicit cast when precision is lost
                                             // (1500ms -> 1s, дробная часть отброшена)// (1500ms -> 1s, the fractional part is dropped)

seconds s2(2);
milliseconds ms2 = s2;                      // неявное расширение — без потери// implicit widening — the compiler allows it
                                             // точности компилятор разрешает implicit// implicitly since no precision is lost

ГраблиGotchas

system_clock не монотонны — не для измерения интерваловsystem_clock isn't monotonic — don't use it for measuring intervals

system_clock::now() может «прыгнуть» назад при корректировке системного времени (NTP-синхронизация, ручной перевод часов) — вычитание двух time_point от system_clock для измерения длительности операции может дать отрицательный или неожиданно большой результат. Для интервалов — только steady_clock.system_clock::now() can jump backward when the system time is adjusted (NTP sync, a manual clock change) — subtracting two system_clock time_points to measure an operation's duration can yield a negative or unexpectedly large result. Use steady_clock for intervals, always.

duration_cast от мелкой единицы к крупной отбрасывает остатокduration_cast from a finer unit to a coarser one truncates the remainder

duration_cast<seconds>(milliseconds(1500)) даёт 1 секунду, а не 1.5 — целочисленное усечение, как при обычном делении int. Если нужна дробная точность, использовать duration с плавающей точкой в Rep (например, duration<double>), а не duration_cast между целочисленными типами.duration_cast<seconds>(milliseconds(1500)) yields 1 second, not 1.5 — integer truncation, the same as ordinary int division. If fractional precision is needed, use a floating-point Rep in the duration (e.g. duration<double>) rather than duration_cast between integer types.

high_resolution_clock — не гарантированно самые точные и не гарантированно монотонные часыhigh_resolution_clock isn't guaranteed to be the most precise or even monotonic

Стандарт не требует от high_resolution_clock ни максимальной точности, ни монотонности — на практике в некоторых реализациях (например, старых libstdc++) high_resolution_clock — просто alias на system_clock. Явный выбор steady_clock для интервалов надёжнее, чем полагаться на имя high_resolution_clock.The standard requires neither maximum precision nor monotonicity from high_resolution_clock — in practice, in some implementations (older libstdc++, for instance) high_resolution_clock is just an alias for system_clock. Explicitly picking steady_clock for intervals is more reliable than trusting the name high_resolution_clock.