Работа со временем как с безразмерной величиной может приводить к недоразумениям и ошибкам конвертации временных единиц измерения:
– Слушай, ты не помнишь, мы в sleep передаем секунды или миллисекунды?
– Блин, оказывается у меня в часе 360 секунд, ноль пропустил.
Для избежания таких ошибок предусмотрена библиотека chrono (namespace std::chrono). Она была добавлена в C++11 и дорабатывалась в поздних стандартах. Теперь все логично:
using namespace std::chrono; int find_answer_to_the_ultimate_question_of_life() { //Поиск ответа std::this_thread::sleep_for(5s); //5 секунд return 42; } std::future<int> f = std::async(find_answer_to_the_ultimate_question_of_life); //Ждем максимум 2.5 секунд if (f.wait_for(2500ms) == std::future_status::ready) std::cout << "Answer is: " << f.get() << "\n"; else std::cout << "Can't wait anymore\n";
Библиотека реализует следующие концепции:
- интервалы времени –
duration; - моменты времени –
time_point; - таймеры –
clock.
std::ratio
std::ratio – шаблонный класс, реализующий compile-time натуральную дробь. Он не относится к chrono, но активно используется этой библиотекой, поэтому, в первую очередь, познакомимся с ним, чтобы далее не вызывал вопросов.
template< std::intmax_t Num, //Числитель std::intmax_t Denom = 1 //Знаменатель > class ratio;
Важно, что числитель и знаменатель – шаблонные constexpr параметры. Это позволяет формировать тип на этапе компиляции. Этот класс вспомогательный (чисто статический, helper class), и вообще говоря, не предназначен для математических вычислений. Он нужен для эффективного перевода единиц измерений. Например, мы хотим работать с различными единицами расстояний:
template<class _Ratio> class Length { double length_; public: explicit Length(double length) : length_(length) { } double length() const { return length_; } }; Length<Mm> len1(127.0); Length<Inches> len2(5.0); Length<Mm> len3 = len1 + len2;
Пусть миллиметр будет базовой единицей, тогда:
using Mm = std::ratio<1>; //Знаменатель == 1 //Также пользователь может определить те, которые ему нужны: using Inches = std::ratio<254, 10>; using Metre = std::ratio<1000, 1>;
В конструкторе можно было производить преобразование к базовой единице. Но, правильнее только там, где это преобразование нужно. Потому что метры в миллиметры можно преобразовать не опасаясь потерь при округлении, чего нельзя сказать об обратном.
В связи с вышесказанным, только лишь для полноты примера, я привожу не самую удачную реализацию операции сложения, зато простую:
template<class _Ratio1, class _Ratio2> Length<Mm> operator+(const Length<_Ratio1> &left, const Length<_Ratio2> &right) { double len = left.length() / _Ratio1::den * _Ratio1::num + right.length() / _Ratio2::den * _Ratio2::num; return Length<Mm>((int)len); }
Правильно было бы получать метры при сложении метров и километров.
duration — интервал времени
Шаблонный класс std::chrono::duration является типом интервала времени. Интервал времени в chrono — это некоторое количество периодов (в оригинале tick period). Это количество характеризуется типом, например int64_t или float. Продолжительность периода измеряется в секундах и представляется в виде натуральной дроби с помощью std::ratio.
Некоторые популярные интервалы уже определены в библиотеке. Типы могут немного различаться в различных реализациях
using nanoseconds = duration<long long, nano>; using microseconds = duration<long long, micro>; using milliseconds = duration<long long, milli>; using seconds = duration<long long>; using minutes = duration<int, ratio<60> >; using hours = duration<int, ratio<3600> >; //Приставки nano, micro, milli: using nano = ratio<1, 1000000000>; using micro = ratio<1, 1000000>; using milli = ratio<1, 1000>;
Но можно определить свои:
using namespace std::chrono; //3-минутные песочные часы using Hourglass = duration<long, std::ratio<180>>; //или using Hourglass = duration<long, std::ratio_multiply<std::ratio<3>, minutes::period>>; //А может вам удобно считать по 2.75 секунд using MyTimeUnit = duration<long, std::ratio<11, 4>>; //Нецелое количество секунд. Иногда полезно using fseconds = duration<float>; //Для какой-нибудь специфичной платформы using seconds16 = duration<uint16_t>;
Теперь как с ними работать. Неявная инициализация запрещена:
seconds s = 5; //Ошибка void foo(minutes); foo(42); //Ошибка
Только явная:
seconds s{8}; void foo(minutes); foo(minutes{42});
Кстати, почему используются фигурные скобки можете почитать, например, здесь. Вкратце: для избежания неявного преобразования интегральных типов с потерями. Добавлю еще случай, когда T x(F()); вместо инициализации x, трактуется как объявление функции, принимающей указатель на функцию типа F(*)() и возвращающей T. Решение: T x{F()}; или T x((F()));.
В C++14 добавлены пользовательские литералы для основных единиц:
seconds s = 4min; void foo(minutes); foo(42min);
Можно складывать, вычитать и сравнивать:
seconds time1 = 5min + 17s; minutes time2 = 2h - 15min; bool less = 59s < 1min;
Как в примере выше, можно неявно преобразовывать часы в минуты, минуты в секунды, секунды в миллисекуну и т. д., но не наоборот:
minutes time3 = 20s; //Ошибка при компиляции seconds time4 = 2s + 500ms; //Ошибка при компиляции
В общем случае, неявное преобразование для целочисленных типов разрешено если отношение периодов является целым числом:
//(20/15) / (1/3) = 4. Ок! duration<long, std::ratio<1, 3>> t1 = duration<long, std::ratio<20, 15>>{ 1 };
В противном случае есть 2 способа: округление и преобразование к float-типу.
//Отбрасывание дробной части - округление в сторону нуля minutes m1 = duration_cast<minutes>(-100s); //-1m //C++17. Округление в сторону ближайшего целого minutes m2 = round<minutes>(-100s); //-2m //C++17. Округление в сторону плюс бесконечности minutes m3 = ceil<minutes>(-100s); //-1m //C++17. Округление в сторону минус бесконечности minutes m4 = floor<minutes>(-100s); //-2m
Второй вариант:
using fminutes = duration<float, minutes::period>; fminutes m = -100s;
Допустим, для вас избыточно представление количества секунд типом uint64_t. Ок:
using seconds16 = duration<uint16_t, seconds::period>; seconds16 s = 15s;
Но вы все равно опасаетесь переполнения. Можно использовать класс из библиотеки для безопасной работы с числами. В стандарте такой нет (только предложение), но есть сторонние реализации. Также есть в VS, ее и используем:
#include <safeint.h> using sint = msl::utilities::SafeInt<uint16_t>; using safe_seconds16 = duration<sint, seconds::period>; safe_seconds16 ss = 60000s; try { ss += 10000s; } catch (msl::utilities::SafeIntException e) { //Ой };
Чтобы вывести значение интервала на экран или в файл, нужно использовать count():
seconds s = 15s; std::cout << s.count() << "s\n";
Но не используйте count для внутренних преобразований!
time_point — момент времени
Класс time_point предназначен для представления моментов времени. Момент времени может быть охарактеризован как интервал времени, измеренным на каком-либо таймере, начиная с некоторой точки отсчета. Например, если вы готовите суп, пользуясь секундомером, то ваши моменты времени могут быть представлены так:
0 сек: добавить в кастрюлю пассерованные овощи 420 сек: положить картофель 1300 сек: готово
А если по минутной стрелке настенных часов, то те же моменты времени могут быть такими:
17 мин: добавить в кастрюлю пассерованные овощи 24 мин: положить картофель 39 мин: готово
Итак, сам класс:
template< class Clock, class Duration = typename Clock::duration > class time_point;
Тип интервала времени нам уже знаком, теперь перейдем к таймеру Clock. В библиотеке 3 таймера:
- system_clock – представляет время системы. Обычно этот таймер не подходит для измерения интервалов, так как во время измерения время может быть изменено пользователем или процессом синхронизации. Обычно основывается на количестве времени, прошедших с 01.01.1970, но это не специфицировано.
- steady_clock – представляет так называемые монотонные часы, то есть ход которых не подвержен внешним изменениям. Хорошо подходит для измерения интервалов. Обычно его реализация основывается на времени работы системы после включения.
- high_resolution_clock – таймер с минимально возможным периодом отсчетов, доступным системе. Может являтся псевдонимом для одного из рассмотренных (почти наверняка это steady_clock).
У Clock есть статическая переменная is_steady, по который вы можете узнать, является ли таймер монотонным. Также у Clock есть функция now, возвращающая текущий момент времени в виде time_point. Сам по себе объект класса time_point не очень интересен, так как момент его начала отсчета не специфирован и имеет мало смысла. Но к нему можно прибавлять интервалы времени и сравнивать с другими моментами времени:
time_point<steady_clock> start = steady_clock::now(); //или steady_clock::time_point start = steady_clock::now(); //или auto start = steady_clock::now(); foo(); if (steady_clock::now() < start + 1s) std::cout << "Less than a second!\n";
time_point нельзя сложить с time_point, зато можно вычесть, что полезно для засечения времени:
auto start = steady_clock::now(); foo(); auto end = steady_clock::now(); auto elapsed = duration_cast<milliseconds>(end - start);
Чтобы получить интервал времени, прошедший с момента начала отсчета, можно вызвать time_since_epoch:
auto now = system_clock::now(); system_clock::duration tse = now.time_since_epoch();
Преобразование time_point в число, например для сериализации или вывода на экран, можно осуществить через С-тип time_t:
auto now = system_clock::now(); time_t now_t = system_clock::to_time_t(now); auto now2 = system_clock::from_time_t(now_t);
Вместо заключения
Самый частый вопрос: как вывести время и дату в читаемом виде. С помощью chrono никак. Можно поиграть с time_t или использовать другую библиотеку от разработчика chrono.
ссылка на оригинал статьи https://habrahabr.ru/post/324984/
Добавить комментарий