О библиотеке
Для работы со временем в STD, начиная со стандарта C++11, появилась библиотека времени – Chrono. До выхода C++11 в STD была только одна библиотека работающая со временем – это Си-библиотека ctime
. Она доступна и сейчас. Но время не стоит на месте. Новые скорости – новое время. Разработчики chrono
хотели создать библиотеку решающую ряд недостатков сишной библиотеки, прежде всего – точность интервалов времени, часы, которые бы использовали такты времени, не связанные с системным временем. Библиотеку chrono составляют три основных типа:
- Интервалы (
duration
) - Моменты (
time_point
) - Часы (
clock
)
Определение интервалов. Часы
В задачах мы ограничимся использованием часов system_clock
– системные часы реального времени (всего же используются три типа часов; в C++20 будет добавлено ещё 6). Поскольку эти часы связаны с системными часами, то они могут быть не монотонными, так как системное время может быть в любой момент синхронизировано. Методов всего три:
-
now()
– текущий момент времени -
to_time_t()
– преобразует момент времени в типtime_t
-
from_time_t()
– преобразует типtime_t
в момент времени системных часов
time_t
– арифметический тип, представляет собой целочисленное значение – число секунд, прошедших с 00:00, 1 января 1970 UTC (POSIX время или Время UNIX).Первая задача, которую нужно решить – это определение временного интервала работы определенного фрагмента программы. Идея проста – получаем первый момент времени до исследуемого фрагмента кода, а второй после. Вычисляем разницу обоих моментов и получаем возможность судить о скорости работы алгоритма.
Программа 1.
#include <iostream> #include <chrono> using namespace std; using namespace chrono; int main() { // Получаем момент времени_1 system_clock::time_point start = system_clock::now(); // Выполняем некоторый код for (long i = 1; i < 10000000000; i += 1); // Получаем момент времени_2 system_clock::time_point end = system_clock::now(); // Определяем тип объекта интервала и вычисляем его значение duration<double> sec = end - start; // вычисляем количество тактов в интервале // и выводим итог cout << sec.count() << " сек." << endl; return 0; }
Возможный вывод программы:
13.7713 сек.
Чтобы получить текущий момент времени необходимо создать объект начального момента времени (start), инициализировав его значением, которое возвращает метод now()
. Для system_clock
– это текущий момент системного времени. В программе 1, для лаконичности определений, мы используем пространство имен chrono
:
using namespace chrono;
Чтобы сделать выражение ещё более компактным, можно воспользоваться спецификатором auto
:
auto start = system_clock::now();
Далее, определяем интервал, как разность двух моментов. В шаблонном параметре типа объекта duration
указывается тип значения интервала. В данном случае – это действительное число.
Чтобы вывести итоговое значение нам нужно знать количество тактов. За такое вычисление берется метод count()
, который и позволяет вывести окончательный результат. Но не всегда такой вывод может нас устроить. Что, если нам нужно определить интервалы с очень большой точностью, скажем в наносекундах? Такая необходимость возникает при тестировании быстрых алгоритмов. Chrono
позволяет использовать на выводе различные единицы – от часов до наносекунд (в зависимости от величины интервала). Для изменения единиц времени используется формат преобразования в стиле C++ duration_cast<>
, которому, в качестве шаблонного параметра, необходимо передать один из возможных типов перечисленных ниже:
std::chrono::nanoseconds
std::chrono::microseconds
std::chrono::milliseconds
std::chrono::seconds
std::chrono::minutes
std::chrono::hours
Изменим немножко код в самом конце:
cout << duration_cast<seconds>(end - start).count() << "\n" << duration_cast<milliseconds>(end - start).count() << "\n" << duration_cast<microseconds>(end - start).count() << "\n" << duration_cast<nanoseconds>(end - start).count() << "\n";
Возможный вывод этого варианта программы:
13 13717 13717877 13717877315
Как вы уже поняли, к интервалам применяются арифметические операции: operator+
, operator-
, operator*
, operator/
, operator%
. Используя операцию "%"
вы можете разделить интервал на разные единицы (как это сделать см. [1, 179]). Также для интервалов доступны все известные операции сравнения.
Chrono и генератор случайных чисел
Переходим к следующему вопросу. Как передать в качестве начального значения генератору случайных чисел момент времени полученный в chrono
, который будет создавать всегда новый набор случайных чисел с каждым запуском программы? На самом деле, тип time_point
– это абстрактный тип основанный на std::ratio
, таким образом, к фундаментальному типу его преобразовать не получится. Вот почему в программе 1 велись такие многоступенчатые преобразования. Следовательно, нам нужно сразу получить готовое число, представляющее некоторый интервал. Такая возможность имеется, если мы возьмем за начальный момент – начало Эпохи UNIX, а конечный, собственно, текущий момент системного времени. Чтобы не заниматься этими вычислениями самостоятельно, проще взять метод класса time_point
– time_since_epoch()
, который возвращает количество тактов от начала эпохи до определенного момента (в данном случае момент системного времени). А затем использовать метод count()
для получения нужного нам числа. В программе ниже используется генератор случайных чисел С++ (библиотека random):
Программа 2
#include <iostream> #include <chrono> #include <random> using namespace std; using namespace chrono; int main() { long seed = system_clock::now().time_since_epoch().count(); default_random_engine rnd(seed); uniform_int_distribution<int> d(1, 100); for (int i = 0; i < 2; i++) { for (int j = 0; j < 10; j++) cout << d(rnd) << " "; cout << "\n"; } return 0; }
Возможный вывод программы:
99 54 90 40 16 57 19 43 87 73 43 46 94 72 90 88 94 40 14 71
Форматированный вывод даты/времени
А вот с форматированным выводом у chrono проблемы. Разработчики не посчитали нужным добавить человеко понятный вывод. Ничего не остается, как воспользоваться возможностями старой ctime
. Но и это еще не все. Чтобы вывод соответствовал региональным традициям необходимо использовать библиотеку local
. Собственно, форматирование вывода производит библиотека ios, которая перехватывает передающиеся ей от ctime данные для форматирования, а дальше, как говорится, дело техники. Нам нужно от chrono две вещи: текущий момент системного времени и, упомянутый выше, метод преобразования to_time_t()
, который преобразовывает момент времени в тип time_t
(с ним может работать ctime
). В итоге, программный код форматирования даты/времени может быть представлен в следующем виде:
Программа 3
#include <chrono> #include <ctime> // localtime #include <iomanip> // put_time #include <iostream> using namespace std; using namespace chrono; int main() { auto now = system_clock::now(); auto my_time = system_clock::to_time_t(now); cout.imbue(locale("ru_RU.utf8")); cout << put_time(localtime(&my_time), "%c %Z") << endl; return 0; }
Возможный вывод программы:
Пт 25 янв 2019 23:31:52 MSK
В грядущем стандарте С++20 функции форматирования даты/времени в chrono
всё же были заявлены [5].
Ссылки, литература
- Джосаттис Н. М. Стандартная библиотека C++: справочное руководство, 2-е изд.-М.: Вильямс, 2014
- https://stackoverflow.com/questions/17223096/outputting-date-and-time-in-c-using-stdchrono
- https://www.boost.org/doc/libs/1_69_0/doc/html/chrono.html
- https://ru.cppreference.com/w/cpp/io/manip/put_time
- https://en.cppreference.com/w/cpp/chrono/format